diff --git a/.backportrc.json b/.backportrc.json index e44d3ce1142993..384e221329a4f6 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.12", "7.11", "7.10", "7.9", @@ -29,7 +30,9 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.12.0$": "7.x", + "^v7.13.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" - } + }, + "autoMerge": true, + "autoMergeMethod": "squash" } diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index b804e3071448e7..0aa962a58f53cc 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -10,7 +10,7 @@ kibanaPipeline(timeoutMinutes: 300) { "TIME_STAMP=${timestamp}", 'CODE_COVERAGE=1', // Enables coverage. Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc. ]) { - workers.base(name: 'coverage-worker', size: 'l', ramDisk: false, bootstrapped: false) { + workers.base(name: 'coverage-worker', size: 'xl', ramDisk: false, bootstrapped: false) { catchError { kibanaPipeline.bash(""" diff --git a/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc new file mode 100644 index 00000000000000..17e7ea1b7672a3 --- /dev/null +++ b/docs/api/actions-and-connectors.asciidoc @@ -0,0 +1,30 @@ +[[actions-and-connectors-api]] +== Action and connector APIs + +Manage Actions and Connectors. + +The following action APIs are available: + +* <> to retrieve a single action by ID + +* <> to retrieve all actions + +* <> to retrieve a list of all action types + +* <> to create actions + +* <> to update the attributes for an existing action + +* <> to execute an action by ID + +* <> to delete an action by ID + +For information about the actions and connectors that {kib} supports, refer to <>. + +include::actions-and-connectors/get.asciidoc[] +include::actions-and-connectors/get_all.asciidoc[] +include::actions-and-connectors/list.asciidoc[] +include::actions-and-connectors/create.asciidoc[] +include::actions-and-connectors/update.asciidoc[] +include::actions-and-connectors/execute.asciidoc[] +include::actions-and-connectors/delete.asciidoc[] diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc new file mode 100644 index 00000000000000..af5ddd050e40ec --- /dev/null +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -0,0 +1,68 @@ +[[actions-and-connectors-api-create]] +=== Create action API +++++ +Create action API +++++ + +Creates an action. + +[[actions-and-connectors-api-create-request]] +==== Request + +`POST :/api/actions/action` + +[[actions-and-connectors-api-create-request-body]] +==== Request body + +`name`:: + (Required, string) The display name for the action. + +`actionTypeId`:: + (Required, string) The action type ID for the action. + +`config`:: + (Required, object) The configuration for the action. Configuration properties vary depending on + the action type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The secrets configuration for the action. Secrets configuration properties vary + depending on the action type. For information about the secrets configuration properties, refer to <>. + +[[actions-and-connectors-api-create-request-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-create-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/actions/action -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "name": "my-action", + "actionTypeId": ".index", + "config": { + "index": "test-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- \ No newline at end of file diff --git a/docs/api/actions-and-connectors/delete.asciidoc b/docs/api/actions-and-connectors/delete.asciidoc new file mode 100644 index 00000000000000..e90b9ae44c5bd3 --- /dev/null +++ b/docs/api/actions-and-connectors/delete.asciidoc @@ -0,0 +1,35 @@ +[[actions-and-connectors-api-delete]] +=== Delete action API +++++ +Delete action API +++++ + +Deletes an action by ID. + +WARNING: When you delete an action, _it cannot be recovered_. + +[[actions-and-connectors-api-delete-request]] +==== Request + +`DELETE :/api/actions/action/` + +[[actions-and-connectors-api-delete-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-delete-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X DELETE api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +-------------------------------------------------- +// KIBANA + diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc new file mode 100644 index 00000000000000..12f1405eb44560 --- /dev/null +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -0,0 +1,83 @@ +[[actions-and-connectors-api-execute]] +=== Execute action API +++++ +Execute action API +++++ + +Executes an action by ID. + +[[actions-and-connectors-api-execute-request]] +==== Request + +`POST :/api/actions/action//_execute` + +[[actions-and-connectors-api-execute-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-execute-request-body]] +==== Request body + +`params`:: + (Required, object) The parameters of the action. Parameter properties vary depending on + the action type. For information about the parameter properties, refer to <>. + +[[actions-and-connectors-api-execute-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-execute-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "params": { + "documents": [ + { + "id": "test_doc_id", + "name": "test_doc_name", + "message": "hello, world" + } + ] + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "status": "ok", + "data": { + "took": 197, + "errors": false, + "items": [ + { + "index": { + "_index": "updated-index", + "_id": "iKyijHcBKCsmXNFrQe3T", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1, + "status": 201 + } + } + ] + }, + "actionId": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" +} +-------------------------------------------------- \ No newline at end of file diff --git a/docs/api/actions-and-connectors/get.asciidoc b/docs/api/actions-and-connectors/get.asciidoc new file mode 100644 index 00000000000000..6be554e65db049 --- /dev/null +++ b/docs/api/actions-and-connectors/get.asciidoc @@ -0,0 +1,50 @@ +[[actions-and-connectors-api-get]] +=== Get action API +++++ +Get action API +++++ + +Retrieves an action by ID. + +[[actions-and-connectors-api-get-request]] +==== Request + +`GET :/api/actions/action/` + +[[actions-and-connectors-api-get-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-get-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-get-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get_all.asciidoc b/docs/api/actions-and-connectors/get_all.asciidoc new file mode 100644 index 00000000000000..9863963c8395e6 --- /dev/null +++ b/docs/api/actions-and-connectors/get_all.asciidoc @@ -0,0 +1,52 @@ +[[actions-and-connectors-api-get-all]] +=== Get all actions API +++++ +Get all actions API +++++ + +Retrieves all actions. + +[[actions-and-connectors-api-get-all-request]] +==== Request + +`GET :/api/actions` + +[[actions-and-connectors-api-get-all-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-get-all-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +[ + { + "id": "preconfigured-mail-action", + "actionTypeId": ".email", + "name": "email: preconfigured-mail-action", + "isPreconfigured": true + }, + { + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false + } +] +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/list.asciidoc b/docs/api/actions-and-connectors/list.asciidoc new file mode 100644 index 00000000000000..b800b7ff3b4f2f --- /dev/null +++ b/docs/api/actions-and-connectors/list.asciidoc @@ -0,0 +1,59 @@ +[[actions-and-connectors-api-list]] +=== List action types API +++++ +List all action types API +++++ + +Retrieves a list of all action types. + +[[actions-and-connectors-api-list-request]] +==== Request + +`GET :/api/actions/list_action_types` + +[[actions-and-connectors-api-list-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-list-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/actions/list_action_types +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +[ + { + "id": ".email", <1> + "name": "Email", <2> + "minimumLicenseRequired": "gold", <3> + "enabled": false, <4> + "enabledInConfig": true, <5> + "enabledInLicense": false <6> + }, + { + "id": ".index", + "name": "Index", + "minimumLicenseRequired": "basic", + "enabled": true, + "enabledInConfig": true, + "enabledInLicense": true + } +] +-------------------------------------------------- + + +<1> `id` - The unique ID of the action type. +<2> `name` - The name of the action type. +<3> `minimumLicenseRequired` - The license required to use the action type. +<4> `enabled` - Specifies if the action type is enabled or disabled in {kib}. +<5> `enabledInConfig` - Specifies if the action type is enabled or enabled in the {kib} .yml file. +<6> `enabledInLicense` - Specifies if the action type is enabled or disabled in the license. diff --git a/docs/api/actions-and-connectors/update.asciidoc b/docs/api/actions-and-connectors/update.asciidoc new file mode 100644 index 00000000000000..e08ec2f8da1b67 --- /dev/null +++ b/docs/api/actions-and-connectors/update.asciidoc @@ -0,0 +1,68 @@ +[[actions-and-connectors-api-update]] +=== Update action API +++++ +Update action API +++++ + +Updates the attributes for an existing action. + +[[actions-and-connectors-api-update-request]] +==== Request + +`PUT :/api/actions/action/` + +[[actions-and-connectors-api-update-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-update-request-body]] +==== Request body + +`name`:: + (Required, string) The new name of the action. + +`config`:: + (Required, object) The new action configuration. Configuration properties vary depending on the action type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The updated secrets configuration for the action. Secrets properties vary depending on the action type. For information about the secrets configuration properties, refer to <>. + +[[actions-and-connectors-api-update-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-update-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X PUT api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "name": "updated-action", + "config": { + "index": "updated-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "updated-action", + "config": { + "index": "updated-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 017e3ec57d3404..54c065480b113c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -53,6 +53,8 @@ readonly links: { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index dc6804b0630bd4..0bca16a0bb7107 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,4 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md index d7eafdce017e43..551cbe3c937504 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md @@ -19,7 +19,6 @@ kibanaResponseFactory: { forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 3ec63840a67cba..d14e41cfb56ecd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -283,7 +283,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginOpaqueId](./kibana-plugin-core-server.pluginopaqueid.md) | | | [PublicUiSettingsParams](./kibana-plugin-core-server.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) exposed to the client-side. | | [RedirectResponseOptions](./kibana-plugin-core-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | -| [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. | +| [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. If anything else is returned, or an error is thrown, the HTTP service will automatically log the error and respond 500 - Internal Server Error. | | [RequestHandlerContextContainer](./kibana-plugin-core-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | | [RequestHandlerContextProvider](./kibana-plugin-core-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | | [RequestHandlerWrapper](./kibana-plugin-core-server.requesthandlerwrapper.md) | Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandler.md) function. | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md index 0032e52a0e9060..d32ac4d80c337b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md @@ -4,7 +4,7 @@ ## RequestHandler type -A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. +A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. If anything else is returned, or an error is thrown, the HTTP service will automatically log the error and respond `500 - Internal Server Error`. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md index 3cc02c404c8d71..17f52687243321 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md @@ -25,7 +25,7 @@ const page1 = await savedObjectsClient.find({ type: 'visualization', sortField: 'updated_at', sortOrder: 'asc', - pit, + pit: { id }, }); const lastHit = page1.saved_objects[page1.saved_objects.length - 1]; const page2 = await savedObjectsClient.find({ diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md index 63956ebee68f7b..6b668824845202 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md @@ -29,19 +29,16 @@ openPointInTimeForType(type: string | string[], { keepAlive, preference }?: Save ```ts -const repository = coreStart.savedObjects.createInternalRepository(); - -const { id } = await repository.openPointInTimeForType( - type: 'index-pattern', - { keepAlive: '2m' }, +const { id } = await savedObjectsClient.openPointInTimeForType( + type: 'visualization', + { keepAlive: '5m' }, ); const page1 = await savedObjectsClient.find({ type: 'visualization', sortField: 'updated_at', sortOrder: 'asc', - pit, + pit: { id, keepAlive: '2m' }, }); - const lastHit = page1.saved_objects[page1.saved_objects.length - 1]; const page2 = await savedObjectsClient.find({ type: 'visualization', @@ -50,7 +47,6 @@ const page2 = await savedObjectsClient.find({ pit: { id: page1.pit_id }, searchAfter: lastHit.sort, }); - await savedObjectsClient.closePointInTime(page2.pit_id); ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md index b0ccedb819c95d..856e43588ffb78 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md @@ -9,15 +9,8 @@ returns all search source fields Signature: ```typescript -getFields(recurse?: boolean): SearchSourceFields; +getFields(): SearchSourceFields; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| recurse | boolean | | - Returns: `SearchSourceFields` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md index 3250561c8b82e9..b2382d35f7d768 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -35,7 +35,7 @@ export declare class SearchSource | [fetch(options)](./kibana-plugin-plugins-data-public.searchsource.fetch.md) | | Fetch this source and reject the returned Promise on error | | [fetch$(options)](./kibana-plugin-plugins-data-public.searchsource.fetch_.md) | | Fetch this source from Elasticsearch, returning an observable over the response(s) | | [getField(field, recurse)](./kibana-plugin-plugins-data-public.searchsource.getfield.md) | | Gets a single field from the fields | -| [getFields(recurse)](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | +| [getFields()](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | | [getId()](./kibana-plugin-plugins-data-public.searchsource.getid.md) | | returns search source id | | [getOwnField(field)](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) | | Get the field from our own fields, don't traverse up the chain | | [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md index 683a35fabf5710..1d4547bb21d103 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md @@ -24,6 +24,7 @@ export interface SearchSourceFields | [highlight](./kibana-plugin-plugins-data-public.searchsourcefields.highlight.md) | any | | | [highlightAll](./kibana-plugin-plugins-data-public.searchsourcefields.highlightall.md) | boolean | | | [index](./kibana-plugin-plugins-data-public.searchsourcefields.index.md) | IndexPattern | | +| [parent](./kibana-plugin-plugins-data-public.searchsourcefields.parent.md) | SearchSourceFields | | | [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | [Query](./kibana-plugin-plugins-data-public.query.md) | | [searchAfter](./kibana-plugin-plugins-data-public.searchsourcefields.searchafter.md) | EsQuerySearchAfter | | | [size](./kibana-plugin-plugins-data-public.searchsourcefields.size.md) | number | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md new file mode 100644 index 00000000000000..3adb34a50ff9eb --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) > [parent](./kibana-plugin-plugins-data-public.searchsourcefields.parent.md) + +## SearchSourceFields.parent property + +Signature: + +```typescript +parent?: SearchSourceFields; +``` diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index d7a9373a6e2a99..3562be1e405f6f 100644 --- a/docs/user/alerting/action-types/email.asciidoc +++ b/docs/user/alerting/action-types/email.asciidoc @@ -37,6 +37,7 @@ Password:: password for 'login' type authentication. password: passwordkeystorevalue -- +[[email-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc index 2c6da7c7c3026b..2f459edea28f14 100644 --- a/docs/user/alerting/action-types/index.asciidoc +++ b/docs/user/alerting/action-types/index.asciidoc @@ -30,6 +30,7 @@ Execution time field:: This field will be automatically set to the time the ale executionTimeField: somedate -- +[[index-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/jira.asciidoc b/docs/user/alerting/action-types/jira.asciidoc index 65e5ee4fc4a013..6e47d5618d5982 100644 --- a/docs/user/alerting/action-types/jira.asciidoc +++ b/docs/user/alerting/action-types/jira.asciidoc @@ -33,6 +33,7 @@ API token (or password):: Jira API authentication token (or password) for HTTP apiToken: tokenkeystorevalue -- +[[jira-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index aad192dbddb30f..e1078a55ddd0d5 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -150,6 +150,7 @@ Integration Key:: A 32 character PagerDuty Integration Key for an integration routingKey: testroutingkey -- +[[pagerduty-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `apiURL`, a string that corresponds to *API URL*. diff --git a/docs/user/alerting/action-types/resilient.asciidoc b/docs/user/alerting/action-types/resilient.asciidoc index b5ddb76d49b0cd..112246ab91162e 100644 --- a/docs/user/alerting/action-types/resilient.asciidoc +++ b/docs/user/alerting/action-types/resilient.asciidoc @@ -33,6 +33,7 @@ API key secret:: The authentication key secret for HTTP Basic authentication. apiKeySecret: tokenkeystorevalue -- +[[resilient-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/servicenow.asciidoc b/docs/user/alerting/action-types/servicenow.asciidoc index 0acb92bcdb5ee5..5d8782c14e5815 100644 --- a/docs/user/alerting/action-types/servicenow.asciidoc +++ b/docs/user/alerting/action-types/servicenow.asciidoc @@ -31,6 +31,7 @@ Password:: Password for HTTP Basic authentication. password: passwordkeystorevalue -- +[[servicenow-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc index a1fe7a2521b22a..6a38e5c827ab28 100644 --- a/docs/user/alerting/action-types/slack.asciidoc +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -26,6 +26,7 @@ Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messa webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz' -- +[[slack-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `webhookUrl`, a string that corresponds to *Webhook URL*. diff --git a/docs/user/alerting/action-types/teams.asciidoc b/docs/user/alerting/action-types/teams.asciidoc index 6706dd2e5643fd..e1ce91fc0c1231 100644 --- a/docs/user/alerting/action-types/teams.asciidoc +++ b/docs/user/alerting/action-types/teams.asciidoc @@ -26,6 +26,7 @@ Webhook URL:: The URL of the incoming webhook. See https://docs.microsoft.com/ webhookUrl: 'https://outlook.office.com/webhook/abcd@0123456/IncomingWebhook/abcdefgh/ijklmnopqrstuvwxyz' -- +[[teams-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `webhookUrl`, a string that corresponds to *Webhook URL*. diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index fff6814325ea45..2d626d53d1c77e 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -36,6 +36,7 @@ Password:: An optional password. If set, HTTP basic authentication is used. Cur password: passwordkeystorevalue -- +[[webhook-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index 20f1fc89367f29..2ae83bee1e06c7 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -36,6 +36,7 @@ include::{kib-repo-dir}/api/features.asciidoc[] include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] +include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] include::{kib-repo-dir}/api/url-shortening.asciidoc[] diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index fb91f6a6a1c9a2..50eb81279fc16e 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -7,20 +7,19 @@ {kib} enables you to give shape to your data and navigate the Elastic Stack. With {kib}, you can: -* *Visualize and analyze your data.* -Search for hidden insights, visualize what you've found in charts, gauges, -maps and more, and combine them in a dashboard. - * *Search, observe, and protect.* From discovering documents to analyzing logs to finding security vulnerabilities, {kib} is your portal for accessing these capabilities and more. +* *Visualize and analyze your data.* +Search for hidden insights, visualize what you've found in charts, gauges, +maps and more, and combine them in a dashboard. + * *Manage, monitor, and secure the Elastic Stack.* Manage your indices and ingest pipelines, monitor the health of your Elastic Stack cluster, and control which users have access to which features. - *{kib} is for administrators, analysts, and business users.* As an admin, your role is to manage the Elastic Stack, from creating your deployment to getting {es} data into {kib}, and then @@ -37,103 +36,84 @@ No matter your data, {kib} can help you uncover patterns and relationships and v [[kibana-home-page]] === Where to start -Start with the home page, where you’re guided toward the most common use cases. -For a quick reference of {kib} use cases, refer to <> +Start with the home page, where you’re presented options for adding your data. +You can collect data from an app or service or upload a file that contains your data. +If you’re not ready to use your own data, you can add a sample data set. + +The home page provides access to the *Enterprise Search*, *Observability*, and *Security* solutions, +and everything you need to visualize and analyze your data. [role="screenshot"] image::images/home-page.png[Kibana home page] -The main menu gets you to where you need to go. Like the home page, -the menu is organized by use case. Want to work with your logs, metrics, APM, or -Uptime data? The apps you need are under *Observability*. The main menu also includes -*Recently viewed*, so you can easily access your previously opened apps. - -Hidden by default, you open the main menu by clicking the -hamburger icon. To keep the main menu visible at all times, click the *Dock navigation* item. +To access all of {kib} features, use the main menu. +Open this menu by clicking the +menu icon. To keep the main menu visible at all times, click *Dock navigation*. +For a quick reference of all {kib} features, refer to <> [role="screenshot"] image::images/kibana-main-menu.png[Kibana main menu] [float] -[[kibana-navigation-search]] -=== Search {kib} - -Using the Search field in the global header, you can -search for applications and objects, such as -dashboards and visualizations. - -Search suggestions include deep links into applications, -allowing you to directly navigate to the views you need most. - -[role="screenshot"] -image::images/app-navigation-search.png[Example of searching for apps] - -When searching for objects, you can search by type, name, and tag. -Tags are keywords or labels that you assign to {kib} objects, -so you can classify the objects in a way that is meaningful to you. -You can then quickly search for related objects based on shared tags. +[[extend-your-use-case]] +=== Search, observe, and protect -[role="screenshot"] -image::images/tags-search.png[Example of searching for tags] +Being able to search, observe, and protect your data is a requirement for any analyst. +{kib} provides solutions for each of these use cases. -To get the most from the search feature, follow these tips: +* https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*] enables you to create a search experience for your app, workplace, and website. -* Use the keyboard shortcut—Ctrl+/ on Windows and Linux, Command+/ on MacOS—to focus on the input at any time. +* {observability-guide}/observability-introduction.html[*Elastic Observability*] enables you to monitor and apply analytics in real time +to events happening across all your environments. You can analyze log events, monitor the performance metrics for the host or container +that it ran in, trace the transaction, and check the overall service availability. -* Use the provided syntax keywords. +* Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of +the events and alerts from your environment. Elastic Security helps you defend +your organization from threats before damage and loss occur. + -[cols=2*] -|=== -|Search by type -|`type:dashboard` - -Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` - -|Search by tag -|`tag:mytagname` + -`tag:"tag name with spaces"` - -|Search by type and name -|`type:dashboard my_dashboard_title` - -|Advanced searches -|`tag:(tagname1 or tagname2) my_dashboard_title` + -`type:lens tag:(tagname1 or tagname2)` + -`type:(dashboard or canvas-workpad) logs` + -|=== +[role="screenshot"] +image::siem/images/detections-ui.png[Detections view in Elastic Security] [float] [[visualize-and-analyze]] -=== Analyze your data +=== Visualize and analyze -Data analysis is the core functionality of {kib}. +Data analysis is a core functionality of {kib}. You can quickly search through large amounts of data, explore fields and values, and then use {kib}’s drag-and-drop interface to rapidly build charts, tables, metrics, and more. [role="screenshot"] -image::images/visualization-journey.png[User visualization journey] +image::images/visualization-journey.png[User data analysis journey] [[get-data-into-kibana]] -. *Add data.* The best way to add {es} data to {kib} is to use one of our guided processes, -available from the <>. You can collect data from an app or service, upload a -file, or add a sample data set. +[cols=2*] +|=== -. *Explore.* With <>, you can search your data for hidden +| *1* +| *Add data.* The best way to add {es} data to {kib} is to use one of our guided processes, +available from the <>. + +| *2* +| *Explore.* With <>, you can search your data for hidden insights and relationships. Ask your questions, and then filter the results to just the data you want. -You can also limit your results to the most recent documents added to {es}. +You can limit your results to the most recent documents added to {es}. -. *Visualize.* {kib} provides many options to create visualizations of your data, from +| *3* +| *Visualize.* {kib} provides many options to create visualizations of your data, from aggregation-based data to time series data. <> is your starting point to create visualizations, and then pulling them together to show your data from multiple perspectives. -. *Present.* With <>, you can display your data on a visually +| *4* +| *Present.* With <>, you can display your data on a visually compelling, pixel-perfect workpad. **Canvas** can give your data the “wow” factor needed to impress your CEO and captivate coworkers with a big-screen display. -. *Share.* Ready to <> your findings with a larger audience? {kib} offers many options—embed +| *5* +| *Share.* Ready to <> your findings with a larger audience? {kib} offers many options—embed a dashboard, share a link, export to PDF, and more. +|=== [float] ==== Plot location data on a map @@ -147,7 +127,7 @@ You can also visualize and track movement over space and through time. [float] ==== Model data behavior -To model the behavior of your data, you'll want to use +To model the behavior of your data, you'll use <>. This app can help you extract insights from your data that you might otherwise miss. You can forecast unusual behavior in your time series data. @@ -164,37 +144,17 @@ can help you uncover website vulnerabilities that hackers are targeting, so you can harden your website. Or, you might provide graph-based personalized recommendations to your e-commerce customers. -[float] -[[extend-your-use-case]] -=== Search, observe, and protect - -Being able to search, observe, and protect your data is a requirement for any analyst. -{kib} provides solutions for each of these use cases. - -* https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*] enables you to create a search experience for your app, workplace, and website. - -* {observability-guide}/observability-introduction.html[*Elastic Observability*] enables you to monitor and apply analytics in real time -to events happening across all your environments. You can analyze log events, monitor the performance metrics for the host or container -that it ran in, trace the transaction, and check the overall service availability. - -* Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of -the events and alerts from your environment. Elastic Security helps you defend -your organization from threats before damage and loss occur. -+ -[role="screenshot"] -image::siem/images/detections-ui.png[] - [float] [[manage-all-things-stack]] === Manage all things Elastic Stack -{kib}'s <> takes you under the hood, -so you can twist the levers and turn the knobs. *Stack Management* provides +{kib}'s <> UIs takes you under the hood, +so you can twist the levers and turn the knobs. You'll find guided processes for administering all things Elastic Stack, including data, indices, clusters, alerts, and security. [role="screenshot"] -image::images/intro-management.png[] +image::images/intro-management.png[Index Management view in Stack Management] [float] ==== Manage your data, indices, and clusters @@ -216,8 +176,8 @@ that exists in almost every use case. For example, you might set an alert to not * System resources, such as memory, CPU and disk space, take a dip. * An unusually high number of service requests, suspicious processes, and login attempts occurs. -An alert is triggered when a specified condition is met. For example, -an alert might trigger when the average or max of one of +An alert triggers when a specified condition is met. For example, +you can trigger an alert when the average or max of one of your metrics exceeds a threshold within a specified time frame. When the alert triggers, you can send a notification to a system that is part of @@ -240,7 +200,7 @@ Think of a space as its own mini {kib} installation—it’s isolated from a so you can tailor it to your specific needs without impacting others. [role="screenshot"] -image::images/select-your-space.png[Space selector screen] +image::images/select-your-space.png[Space selector view] Most of {kib}’s entities are space-aware, including dashboards, visualizations, index patterns, Canvas workpads, Timelion visualizations, graphs, tags, and machine learning jobs. @@ -271,7 +231,7 @@ to specific features on a per-user basis, you must configure <>. [role="screenshot"] -image::images/features-control.png[Features Controls screen] +image::images/features-control.png[Features Controls view] [float] [[intro-kibana-Security]] @@ -289,7 +249,7 @@ Kibana supports several <>, allowing you to login using {es}’s built-in realms, or by your own single sign-on provider. [role="screenshot"] -image::images/login-screen.png[Login screen] +image::images/login-screen.png[Login page] [float] ==== Secure access @@ -320,6 +280,52 @@ record of who did what, when. The {kib} audit log will record this information f which can then be correlated with {es} audit logs to gain more insights into your users’ behavior. For more information, see <>. +[float] +[[kibana-navigation-search]] +=== Quickly find apps and objects + +Using the search field in the global header, you can +search for applications and objects, such as +dashboards and visualizations. Search suggestions include deep links into applications, +allowing you to directly navigate to the views you need most. + +[role="screenshot"] +image::images/app-navigation-search.png[Example of searching for apps] + +When searching for objects, you can search by type, name, and tag. +Tags are keywords or labels that you assign to {kib} objects, +so you can classify the objects in a way that is meaningful to you. +You can then quickly search for related objects based on shared tags. + +[role="screenshot"] +image::images/tags-search.png[Example of searching for tags] + +To get the most from the search feature, follow these tips: + +* Use the keyboard shortcut—Ctrl+/ on Windows and Linux, Command+/ on MacOS—to focus on the input at any time. + +* Use the provided syntax keywords. ++ +[cols=2*] +|=== +|Search by type +|`type:dashboard` + +Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` + +|Search by tag +|`tag:mytagname` + +`tag:"tag name with spaces"` + +|Search by type and name +|`type:dashboard my_dashboard_title` + +|Advanced searches +|`tag:(tagname1 or tagname2) my_dashboard_title` + +`type:lens tag:(tagname1 or tagname2)` + +`type:(dashboard or canvas-workpad) logs` + +|=== + [float] [[whats-the-right-app]] === What’s the right app for you? @@ -345,32 +351,6 @@ the <>. |See the full list of {kib} features |The https://www.elastic.co/kibana/features[{kib} features page on elastic.co] -2+| *Analyze and visualize your data* - -|Know what’s in your data -|<> - -|Create charts and other visualizations -|<> - -|Show your data from different perspectives -|<> - -|Work with location data -|<> - -|Create a presentation of your data -|<> - -|Generate models for your data’s behavior -|<> - -|Explore connections in your data -|<> - -|Share your data -|<>, <> - 2+|*Build a search experience* |Create a search experience for your workplace @@ -414,6 +394,32 @@ the <>. |View and manage hosts that are running Endpoint Security |{security-guide}/admin-page-ov.html[Administration] +2+| *Analyze and visualize your data* + +|Know what’s in your data +|<> + +|Create charts and other visualizations +|<> + +|Show your data from different perspectives +|<> + +|Work with location data +|<> + +|Create a presentation of your data +|<> + +|Generate models for your data’s behavior +|<> + +|Explore connections in your data +|<> + +|Share your data +|<>, <> + 2+|*Administer your Kibana instance* |Manage your Elasticsearch data @@ -435,7 +441,7 @@ the <>. [float] [[try-kibana]] -=== Getting help +=== How to get help Using our in-product guidance can help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[Help icon in navigation bar] diff --git a/package.json b/package.json index cc8ab44c51c96a..a65c12fce46990 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@babel/core": "^7.12.10", "@babel/runtime": "^7.12.5", "@elastic/datemath": "link:packages/elastic-datemath", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.2", "@elastic/ems-client": "7.12.0", "@elastic/eui": "31.7.0", "@elastic/filesaver": "1.1.2", @@ -351,7 +351,7 @@ "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", - "@elastic/charts": "24.6.0", + "@elastic/charts": "25.0.1", "@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", @@ -592,7 +592,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^1.10.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^5.6.4", + "backport": "^5.6.6", "base64-js": "^1.3.1", "base64url": "^3.0.1", "broadcast-channel": "^3.0.3", diff --git a/renovate.json5 b/renovate.json5 index 415aa71fc3820f..f8fa3b916946f8 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -11,22 +11,10 @@ ], baseBranches: [ 'master', + '7.x', ], - labels: [ - 'release_note:skip', - 'renovate', - 'v8.0.0', - 'v7.11.0', - ], - major: { - labels: [ - 'release_note:skip', - 'renovate', - 'v8.0.0', - 'v7.11.0', - 'renovate:major', - ], - }, + prConcurrentLimit: 0, + prHourlyLimit: 0, separateMajorMinor: false, masterIssue: true, rangeStrategy: 'bump', @@ -49,12 +37,31 @@ groupName: '@elastic/charts', packageNames: ['@elastic/charts'], reviewers: ['markov00'], + matchBaseBranches: ['master'], + labels: ['release_note:skip', 'v8.0.0', 'v7.13.0'], + enabled: true, + }, + { + groupName: '@elastic/elasticsearch', + packageNames: ['@elastic/elasticsearch'], + reviewers: ['team:kibana-operations'], + matchBaseBranches: ['master'], + labels: ['release_note:skip', 'v8.0.0', 'Team:Operations', 'backport:skip'], + enabled: true, + }, + { + groupName: '@elastic/elasticsearch', + packageNames: ['@elastic/elasticsearch'], + reviewers: ['team:kibana-operations'], + matchBaseBranches: ['7.x'], + labels: ['release_note:skip', 'v7.13.0', 'Team:Operations', 'backport:skip'], enabled: true, }, { groupName: 'vega related modules', packageNames: ['vega', 'vega-lite', 'vega-schema-url-parser', 'vega-tooltip'], reviewers: ['team:kibana-app'], + matchBaseBranches: ['master'], labels: ['Feature:Vega', 'Team:KibanaApp'], enabled: true, }, diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 0d40899544c085..937a89e12b7553 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -72,6 +72,8 @@ export class DocLinksService { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/winlogbeat/${DOC_LINK_VERSION}`, }, aggs: { + composite: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-composite-aggregation.html`, + composite_missing_bucket: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-composite-aggregation.html#_missing_bucket`, date_histogram: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-datehistogram-aggregation.html`, date_range: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-daterange-aggregation.html`, date_format_pattern: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-daterange-aggregation.html#date-format-pattern`, @@ -301,6 +303,8 @@ export interface DocLinksStart { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 2e23b26f636c8f..e29173d1495af7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -523,6 +523,8 @@ export interface DocLinksStart { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/src/core/server/http/base_path_proxy_server.test.ts b/src/core/server/http/base_path_proxy_server.test.ts index 8f3a63058a8ae0..80c03a2af9031b 100644 --- a/src/core/server/http/base_path_proxy_server.test.ts +++ b/src/core/server/http/base_path_proxy_server.test.ts @@ -705,12 +705,8 @@ describe('BasePathProxyServer', () => { options: { body: { output: 'stream' } }, }, (_, req, res) => { - try { - expect(req.body).toBeInstanceOf(Readable); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); @@ -740,15 +736,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -779,15 +771,11 @@ describe('BasePathProxyServer', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -815,15 +803,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -851,15 +835,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 71452cce246dfa..52dab28accb33b 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -151,7 +151,6 @@ const createResponseFactoryMock = (): jest.Mocked => ({ forbidden: jest.fn(), notFound: jest.fn(), conflict: jest.fn(), - internalError: jest.fn(), customError: jest.fn(), }); @@ -162,7 +161,6 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked { options: { body: { parse: false } }, }, (context, req, res) => { - try { - expect(req.body).toBeInstanceOf(Buffer); - expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Buffer); + expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); @@ -1053,15 +1049,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1091,15 +1083,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1128,15 +1116,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1165,15 +1149,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1294,12 +1274,8 @@ test('should return a stream in the body', async () => { options: { body: { output: 'stream' } }, }, (context, req, res) => { - try { - expect(req.body).toBeInstanceOf(Readable); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index c9c4410171b34a..6c54067435405a 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -1672,7 +1672,11 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); - expect(result.body.message).toBe('reason'); + expect(result.body).toEqual({ + error: 'Internal Server Error', + message: 'reason', + statusCode: 500, + }); expect(loggingSystemMock.collect(logger).error).toHaveLength(0); }); diff --git a/src/core/server/http/router/response.ts b/src/core/server/http/router/response.ts index f4e09fa1cc08ea..e2babf719f67e0 100644 --- a/src/core/server/http/router/response.ts +++ b/src/core/server/http/router/response.ts @@ -177,15 +177,6 @@ const errorResponseFactory = { conflict: (options: ErrorHttpResponseOptions = {}) => new KibanaResponse(409, options.body || 'Conflict', options), - // Server error - /** - * The server encountered an unexpected condition that prevented it from fulfilling the request. - * Status code: `500`. - * @param options - {@link HttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client - */ - internalError: (options: ErrorHttpResponseOptions = {}) => - new KibanaResponse(500, options.body || 'Internal Error', options), - /** * Creates an error response with defined status code and payload. * @param options - {@link CustomHttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index acce209751e325..85eab7c0892e88 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -314,6 +314,8 @@ type RequestHandlerEnhanced = WithoutHeadAr /** * A function executed when route path matched requested resource path. * Request handler is expected to return a result of one of {@link KibanaResponseFactory} functions. + * If anything else is returned, or an error is thrown, the HTTP service will automatically log the error + * and respond `500 - Internal Server Error`. * @param context {@link RequestHandlerContext} - the core context exposed for this request. * @param request {@link KibanaRequest} - object containing information about requested resource, * such as path, method, headers, parameters, query, body, etc. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 377cd2bc2068a9..2177da84b2b53d 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1276,7 +1276,6 @@ export const kibanaResponseFactory: { forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; @@ -3197,7 +3196,7 @@ export const validBodyOutput: readonly ["data", "stream"]; // Warnings were encountered during analysis: // -// src/core/server/http/router/response.ts:306:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts +// src/core/server/http/router/response.ts:297:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:283:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/apm_oss/server/tutorial/index_pattern.json b/src/plugins/apm_oss/server/tutorial/index_pattern.json index 93a2393b70fa43..b0a1ac1ccd379b 100644 --- a/src/plugins/apm_oss/server/tutorial/index_pattern.json +++ b/src/plugins/apm_oss/server/tutorial/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { - "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.build.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"}}", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.build.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.service.selectors.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.wall.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/src/plugins/charts/public/static/utils/transform_click_event.ts b/src/plugins/charts/public/static/utils/transform_click_event.ts index e875967616bbdb..0c303b92bf1a1c 100644 --- a/src/plugins/charts/public/static/utils/transform_click_event.ts +++ b/src/plugins/charts/public/static/utils/transform_click_event.ts @@ -30,9 +30,6 @@ export interface BrushTriggerEvent { type AllSeriesAccessors = Array<[accessor: Accessor | AccessorFn, value: string | number]>; -// TODO: replace when exported from elastic/charts -const DEFAULT_SINGLE_PANEL_SM_VALUE = '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__'; - /** * returns accessor value from string or function accessor * @param datum @@ -97,11 +94,11 @@ function getSplitChartValue({ | string | number | undefined { - if (smHorizontalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE) { + if (smHorizontalAccessorValue !== undefined) { return smHorizontalAccessorValue; } - if (smVerticalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE) { + if (smVerticalAccessorValue !== undefined) { return smVerticalAccessorValue; } diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 9e889e85734ee6..030e620bea34b4 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -95,164 +95,6 @@ describe('SearchSource', () => { } `); }); - - test('recurses parents to get the entire filters: plain object filter', () => { - const RECURSE = true; - - const parent = new SearchSource({}, searchSourceDependencies); - parent.setField('filter', [ - { - meta: { - index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', - params: {}, - alias: null, - disabled: false, - negate: false, - }, - query: { - range: { - '@date': { - gte: '2016-01-27T18:11:05.010Z', - lte: '2021-01-27T18:11:05.010Z', - format: 'strict_date_optional_time', - }, - }, - }, - }, - ]); - searchSource.setParent(parent); - searchSource.setField('aggs', 5); - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - - // calling twice gives the same result: no searchSources in the hierarchy were modified - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - }); - - test('recurses parents to get the entire filters: function filter', () => { - const RECURSE = true; - - const parent = new SearchSource({}, searchSourceDependencies); - parent.setField('filter', () => ({ - meta: { - index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', - params: {}, - alias: null, - disabled: false, - negate: false, - }, - query: { - range: { - '@date': { - gte: '2016-01-27T18:11:05.010Z', - lte: '2021-01-27T18:11:05.010Z', - format: 'strict_date_optional_time', - }, - }, - }, - })); - searchSource.setParent(parent); - searchSource.setField('aggs', 5); - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - - // calling twice gives the same result: no double-added filters - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - }); }); describe('#removeField()', () => { @@ -345,8 +187,13 @@ describe('SearchSource', () => { }); test('allows you to override computed fields if you provide a format', async () => { + const indexPatternFields = indexPattern.fields; + indexPatternFields.getByType = (type) => { + return []; + }; searchSource.setField('index', ({ ...indexPattern, + fields: indexPatternFields, getComputedFields: () => ({ storedFields: [], scriptFields: {}, @@ -379,6 +226,11 @@ describe('SearchSource', () => { test('injects a date format for computed docvalue fields while merging other properties', async () => { searchSource.setField('index', ({ ...indexPattern, + fields: { + getByType: () => { + return []; + }, + }, getComputedFields: () => ({ storedFields: [], scriptFields: {}, @@ -625,7 +477,7 @@ describe('SearchSource', () => { searchSource.setField('fields', ['hello', '@timestamp', 'foo-a', 'bar']); const request = await searchSource.getSearchRequestBody(); - expect(request.fields).toEqual(['hello', '@timestamp', 'bar']); + expect(request.fields).toEqual(['hello', '@timestamp', 'bar', 'date']); expect(request.script_fields).toEqual({ hello: {} }); expect(request.stored_fields).toEqual(['@timestamp', 'bar']); }); @@ -681,6 +533,60 @@ describe('SearchSource', () => { }); }); + describe('handling date fields', () => { + test('adds date format to any date field', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: '@timestamp' }], + }), + fields: { + getByType: () => [{ name: '@timestamp', esTypes: ['date_nanos'] }], + }, + getSourceFiltering: () => ({ excludes: [] }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['*']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual([ + '*', + { field: '@timestamp', format: 'strict_date_optional_time_nanos' }, + ]); + }); + + test('adds date format to any date field except the one excluded by source filters', async () => { + const indexPatternFields = indexPattern.fields; + // @ts-ignore + indexPatternFields.getByType = (type) => { + return [ + { name: '@timestamp', esTypes: ['date_nanos'] }, + { name: 'custom_date', esTypes: ['date'] }, + ]; + }; + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: '@timestamp' }, { field: 'custom_date' }], + }), + fields: indexPatternFields, + getSourceFiltering: () => ({ excludes: ['custom_date'] }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['*']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual([ + { field: 'foo-bar' }, + { field: 'field1' }, + { field: 'field2' }, + { field: '@timestamp', format: 'strict_date_optional_time_nanos' }, + ]); + }); + }); + describe(`#setField('index')`, () => { describe('auto-sourceFiltering', () => { describe('new index pattern assigned', () => { @@ -911,4 +817,74 @@ describe('SearchSource', () => { expect(request._source).toEqual(['geometry']); }); }); + + describe('getSerializedFields', () => { + const filter = [ + { + query: 'query', + meta: { + alias: 'alias', + disabled: false, + negate: false, + index: '456', + }, + }, + ]; + + test('should return serialized fields', () => { + const indexPattern123 = { id: '123' } as IndexPattern; + searchSource.setField('index', indexPattern123); + searchSource.setField('filter', () => { + return filter; + }); + const serializedFields = searchSource.getSerializedFields(); + expect(serializedFields).toMatchInlineSnapshot( + { index: '123', filter }, + ` + Object { + "filter": Array [ + Object { + "meta": Object { + "alias": "alias", + "disabled": false, + "index": "456", + "negate": false, + }, + "query": "query", + }, + ], + "index": "123", + } + ` + ); + }); + + test('should support nested search sources', () => { + const indexPattern123 = { id: '123' } as IndexPattern; + searchSource.setField('index', indexPattern123); + searchSource.setField('from', 123); + const childSearchSource = searchSource.createChild(); + childSearchSource.setField('timeout', '100'); + const serializedFields = childSearchSource.getSerializedFields(true); + expect(serializedFields).toMatchInlineSnapshot( + { + timeout: '100', + parent: { + index: '123', + from: 123, + }, + }, + ` + Object { + "index": undefined, + "parent": Object { + "from": 123, + "index": "123", + }, + "timeout": "100", + } + ` + ); + }); + }); }); diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 2cf0455ae2df8b..118bb04c1742b4 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -59,7 +59,7 @@ */ import { setWith } from '@elastic/safer-lodash-set'; -import { uniqueId, keyBy, pick, difference, omit, isFunction, isEqual } from 'lodash'; +import { uniqueId, keyBy, pick, difference, isFunction, isEqual, uniqWith } from 'lodash'; import { map, switchMap, tap } from 'rxjs/operators'; import { defer, from } from 'rxjs'; import { isObject } from 'rxjs/internal-compatibility'; @@ -114,8 +114,13 @@ export class SearchSource { private readonly dependencies: SearchSourceDependencies; constructor(fields: SearchSourceFields = {}, dependencies: SearchSourceDependencies) { - this.fields = fields; + const { parent, ...currentFields } = fields; + this.fields = currentFields; this.dependencies = dependencies; + + if (parent) { + this.setParent(new SearchSource(parent, dependencies)); + } } /** *** @@ -173,49 +178,7 @@ export class SearchSource { /** * returns all search source fields */ - getFields(recurse = false): SearchSourceFields { - let thisFilter = this.fields.filter; // type is single value, array, or function - if (thisFilter) { - if (typeof thisFilter === 'function') { - thisFilter = thisFilter() || []; // type is single value or array - } - - if (Array.isArray(thisFilter)) { - thisFilter = [...thisFilter]; - } else { - thisFilter = [thisFilter]; - } - } else { - thisFilter = []; - } - - if (recurse) { - const parent = this.getParent(); - if (parent) { - const parentFields = parent.getFields(recurse); - - let parentFilter = parentFields.filter; // type is single value, array, or function - if (parentFilter) { - if (typeof parentFilter === 'function') { - parentFilter = parentFilter() || []; // type is single value or array - } - - if (Array.isArray(parentFilter)) { - thisFilter.push(...parentFilter); - } else { - thisFilter.push(parentFilter); - } - } - - // add combined filters to the fields - const thisFields = { - ...this.fields, - filter: thisFilter, - }; - - return { ...parentFields, ...thisFields }; - } - } + getFields(): SearchSourceFields { return { ...this.fields }; } @@ -548,6 +511,37 @@ export class SearchSource { })); } + private getFieldFromDocValueFieldsOrIndexPattern( + docvaluesIndex: Record, + fld: SearchFieldValue, + index?: IndexPattern + ) { + if (typeof fld === 'string') { + return fld; + } + const fieldName = this.getFieldName(fld); + const field = { + ...docvaluesIndex[fieldName], + ...fld, + }; + if (!index) { + return field; + } + const { fields } = index; + const dateFields = fields.getByType('date'); + const dateField = dateFields.find((indexPatternField) => indexPatternField.name === fieldName); + if (!dateField) { + return field; + } + const { esTypes } = dateField; + if (esTypes?.includes('date_nanos')) { + field.format = 'strict_date_optional_time_nanos'; + } else if (esTypes?.includes('date')) { + field.format = 'strict_date_optional_time'; + } + return field; + } + private flatten() { const { getConfig } = this.dependencies; const searchRequest = this.mergeProps(); @@ -657,22 +651,25 @@ export class SearchSource { // if items that are in the docvalueFields are provided, we should // inject the format from the computed fields if one isn't given const docvaluesIndex = keyBy(filteredDocvalueFields, 'field'); - body.fields = this.getFieldsWithoutSourceFilters(index, body.fields).map( - (fld: SearchFieldValue) => { - const fieldName = this.getFieldName(fld); - if (Object.keys(docvaluesIndex).includes(fieldName)) { - // either provide the field object from computed docvalues, - // or merge the user-provided field with the one in docvalues - return typeof fld === 'string' - ? docvaluesIndex[fld] - : { - ...docvaluesIndex[fieldName], - ...fld, - }; - } - return fld; + const bodyFields = this.getFieldsWithoutSourceFilters(index, body.fields); + body.fields = uniqWith( + bodyFields.concat(filteredDocvalueFields), + (fld1: SearchFieldValue, fld2: SearchFieldValue) => { + const field1Name = this.getFieldName(fld1); + const field2Name = this.getFieldName(fld2); + return field1Name === field2Name; } - ); + ).map((fld: SearchFieldValue) => { + const fieldName = this.getFieldName(fld); + if (Object.keys(docvaluesIndex).includes(fieldName)) { + // either provide the field object from computed docvalues, + // or merge the user-provided field with the one in docvalues + return typeof fld === 'string' + ? docvaluesIndex[fld] + : this.getFieldFromDocValueFieldsOrIndexPattern(docvaluesIndex, fld, index); + } + return fld; + }); } } else { body.fields = filteredDocvalueFields; @@ -693,9 +690,7 @@ export class SearchSource { * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) */ public getSerializedFields(recurse = false) { - const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(recurse), [ - 'size', - ]); + const { filter: originalFilters, size: omit, ...searchSourceFields } = this.getFields(); let serializedSearchSourceFields: SearchSourceFields = { ...searchSourceFields, index: (searchSourceFields.index ? searchSourceFields.index.id : undefined) as any, @@ -707,6 +702,9 @@ export class SearchSource { filter: filters, }; } + if (recurse && this.getParent()) { + serializedSearchSourceFields.parent = this.getParent()!.getSerializedFields(recurse); + } return serializedSearchSourceFields; } diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index 61d7165393b099..d06f521640da04 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -99,6 +99,8 @@ export interface SearchSourceFields { searchAfter?: EsQuerySearchAfter; timeout?: string; terminate_after?: number; + + parent?: SearchSourceFields; } export interface SearchSourceOptions { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 745f4a7d29d224..67423295dfe5e3 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2382,7 +2382,7 @@ export class SearchSource { // @deprecated fetch(options?: ISearchOptions): Promise>; getField(field: K, recurse?: boolean): SearchSourceFields[K]; - getFields(recurse?: boolean): SearchSourceFields; + getFields(): SearchSourceFields; getId(): string; getOwnField(field: K): SearchSourceFields[K]; getParent(): SearchSource | undefined; @@ -2428,6 +2428,8 @@ export interface SearchSourceFields { // (undocumented) index?: IndexPattern; // (undocumented) + parent?: SearchSourceFields; + // (undocumented) query?: Query; // Warning: (ae-forgotten-export) The symbol "EsQuerySearchAfter" needs to be exported by the entry point index.d.ts // diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 8a633d8d827f4e..489a23eb83897c 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -57,17 +57,13 @@ export function registerValueSuggestionsRoute( const field = indexPattern && getFieldByName(fieldName, indexPattern); const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); - try { - const result = await client.callAsCurrentUser('search', { index, body }, { signal }); + const result = await client.callAsCurrentUser('search', { index, body }, { signal }); - const buckets: any[] = - get(result, 'aggregations.suggestions.buckets') || - get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); + const buckets: any[] = + get(result, 'aggregations.suggestions.buckets') || + get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); - return response.ok({ body: map(buckets || [], 'key') }); - } catch (error) { - return response.internalError({ body: error }); - } + return response.ok({ body: map(buckets || [], 'key') }); } ); } diff --git a/src/plugins/discover/public/application/angular/context.js b/src/plugins/discover/public/application/angular/context.js index e4128b3e262473..01a28a5c174b6b 100644 --- a/src/plugins/discover/public/application/angular/context.js +++ b/src/plugins/discover/public/application/angular/context.js @@ -65,6 +65,7 @@ function ContextAppRouteController($routeParams, $scope, $route) { storeInSessionStorage: getServices().uiSettings.get('state:storeInSessionStorage'), history: getServices().history(), toasts: getServices().core.notifications.toasts, + uiSettings: getServices().core.uiSettings, }); this.state = { ...appState.getState() }; this.anchorId = $routeParams.id; diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.js b/src/plugins/discover/public/application/angular/context/api/anchor.js index f1074be2ebcf25..83b611cb0d6488 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.js @@ -32,7 +32,7 @@ export function fetchAnchorProvider(indexPatterns, searchSource, useNewFieldsApi .setField('sort', sort); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', ['*']); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); } const response = await searchSource.fetch(); diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.test.js b/src/plugins/discover/public/application/angular/context/api/anchor.test.js index 5b077f7443c27e..12b9b4ab285567 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.test.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.test.js @@ -154,7 +154,7 @@ describe('context app', function () { const removeFieldsSpy = searchSourceStub.removeField.withArgs('fieldsFromSource'); expect(setFieldsSpy.calledOnce).toBe(true); expect(removeFieldsSpy.calledOnce).toBe(true); - expect(setFieldsSpy.firstCall.args[1]).toEqual(['*']); + expect(setFieldsSpy.firstCall.args[1]).toEqual([{ field: '*', include_unmapped: 'true' }]); }); }); }); diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index 4887a0644b6eb6..43f6e83d286b36 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -114,7 +114,7 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields const searchSource = await data.search.searchSource.create(); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', ['*']); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); } return searchSource .setParent(undefined) diff --git a/src/plugins/discover/public/application/angular/context_state.test.ts b/src/plugins/discover/public/application/angular/context_state.test.ts index e7622a13394c13..b49a6546a993d3 100644 --- a/src/plugins/discover/public/application/angular/context_state.test.ts +++ b/src/plugins/discover/public/application/angular/context_state.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { IUiSettingsClient } from 'kibana/public'; import { getState } from './context_state'; import { createBrowserHistory, History } from 'history'; import { FilterManager, Filter } from '../../../../data/public'; import { coreMock } from '../../../../../core/public/mocks'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; const setupMock = coreMock.createSetup(); describe('Test Discover Context State', () => { @@ -23,6 +25,10 @@ describe('Test Discover Context State', () => { defaultStepSize: '4', timeFieldName: 'time', history, + uiSettings: { + get: (key: string) => + ((key === SEARCH_FIELDS_FROM_SOURCE ? true : ['_source']) as unknown) as T, + } as IUiSettingsClient, }); state.startSync(); }); diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts index d109badbdd1646..0bae006ec1f6e4 100644 --- a/src/plugins/discover/public/application/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; import { History } from 'history'; -import { NotificationsStart } from 'kibana/public'; +import { NotificationsStart, IUiSettingsClient } from 'kibana/public'; import { createStateContainer, createKbnUrlStateStorage, @@ -17,6 +17,7 @@ import { withNotifyOnErrors, } from '../../../../kibana_utils/public'; import { esFilters, FilterManager, Filter, Query } from '../../../../data/public'; +import { handleSourceColumnState } from './helpers'; export interface AppState { /** @@ -73,6 +74,11 @@ interface GetStateParams { * kbnUrlStateStorage will use it notifying about inner errors */ toasts?: NotificationsStart['toasts']; + + /** + * core ui settings service + */ + uiSettings: IUiSettingsClient; } interface GetStateReturn { @@ -123,6 +129,7 @@ export function getState({ storeInSessionStorage = false, history, toasts, + uiSettings, }: GetStateParams): GetStateReturn { const stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, @@ -134,7 +141,12 @@ export function getState({ const globalStateContainer = createStateContainer(globalStateInitial); const appStateFromUrl = stateStorage.get(APP_STATE_URL_KEY) as AppState; - const appStateInitial = createInitialAppState(defaultStepSize, timeFieldName, appStateFromUrl); + const appStateInitial = createInitialAppState( + defaultStepSize, + timeFieldName, + appStateFromUrl, + uiSettings + ); const appStateContainer = createStateContainer(appStateInitial); const { start, stop } = syncStates([ @@ -257,7 +269,8 @@ function getFilters(state: AppState | GlobalState): Filter[] { function createInitialAppState( defaultSize: string, timeFieldName: string, - urlState: AppState + urlState: AppState, + uiSettings: IUiSettingsClient ): AppState { const defaultState = { columns: ['_source'], @@ -270,8 +283,11 @@ function createInitialAppState( return defaultState; } - return { - ...defaultState, - ...urlState, - }; + return handleSourceColumnState( + { + ...defaultState, + ...urlState, + }, + uiSettings + ); } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 3733e866989588..78ad40e48fd965 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -110,7 +110,7 @@ app.config(($routeProvider) => { const history = getHistory(); const savedSearchId = $route.current.params.id; return data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { - const { appStateContainer } = getState({ history }); + const { appStateContainer } = getState({ history, uiSettings: config }); const { index } = appStateContainer.getState(); return Promise.props({ ip: loadIndexPattern(index, data.indexPatterns, config), @@ -169,10 +169,10 @@ function discoverController($route, $scope, Promise) { let inspectorRequest; let isChangingIndexPattern = false; const savedSearch = $route.current.locals.savedObjects.savedSearch; - $scope.searchSource = savedSearch.searchSource; + const persistentSearchSource = savedSearch.searchSource; $scope.indexPattern = resolveIndexPattern( $route.current.locals.savedObjects.ip, - $scope.searchSource, + persistentSearchSource, toastNotifications ); $scope.useNewFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); @@ -195,6 +195,7 @@ function discoverController($route, $scope, Promise) { storeInSessionStorage: config.get('state:storeInSessionStorage'), history, toasts: core.notifications.toasts, + uiSettings: config, }); const { @@ -370,25 +371,19 @@ function discoverController($route, $scope, Promise) { }); }; - $scope.searchSource - .setField('index', $scope.indexPattern) - .setField('highlightAll', true) - .setField('version', true); - - // Even when searching rollups, we want to use the default strategy so that we get back a - // document-like response. - $scope.searchSource.setPreferredSearchStrategyId('default'); + persistentSearchSource.setField('index', $scope.indexPattern); // searchSource which applies time range - const timeRangeSearchSource = savedSearch.searchSource.create(); + const volatileSearchSource = savedSearch.searchSource.create(); if (isDefaultType($scope.indexPattern)) { - timeRangeSearchSource.setField('filter', () => { + volatileSearchSource.setField('filter', () => { return timefilter.createFilter($scope.indexPattern); }); } - $scope.searchSource.setParent(timeRangeSearchSource); + volatileSearchSource.setParent(persistentSearchSource); + $scope.volatileSearchSource = volatileSearchSource; const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); @@ -403,7 +398,8 @@ function discoverController($route, $scope, Promise) { } function getStateDefaults() { - const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + const query = + persistentSearchSource.getField('query') || data.query.queryString.getDefaultQuery(); const sort = getSortArray(savedSearch.sort, $scope.indexPattern); const columns = getDefaultColumns(); @@ -415,7 +411,7 @@ function discoverController($route, $scope, Promise) { columns, index: $scope.indexPattern.id, interval: 'auto', - filters: _.cloneDeep($scope.searchSource.getOwnField('filter')), + filters: _.cloneDeep(persistentSearchSource.getOwnField('filter')), }; if (savedSearch.grid) { defaultState.grid = savedSearch.grid; @@ -556,7 +552,7 @@ function discoverController($route, $scope, Promise) { .then(function () { $scope.fetchStatus = fetchStatuses.LOADING; logInspectorRequest({ searchSessionId }); - return $scope.searchSource.fetch({ + return $scope.volatileSearchSource.fetch({ abortSignal: abortController.signal, sessionId: searchSessionId, }); @@ -603,11 +599,13 @@ function discoverController($route, $scope, Promise) { } function onResults(resp) { - inspectorRequest.stats(getResponseInspectorStats(resp, $scope.searchSource)).ok({ json: resp }); + inspectorRequest + .stats(getResponseInspectorStats(resp, $scope.volatileSearchSource)) + .ok({ json: resp }); if (getTimeField() && !$scope.state.hideChart) { const tabifiedData = tabifyAggResponse($scope.opts.chartAggConfigs, resp); - $scope.searchSource.rawResponse = resp; + $scope.volatileSearchSource.rawResponse = resp; $scope.histogramData = discoverResponseHandler( tabifiedData, getDimensions($scope.opts.chartAggConfigs.aggs, $scope.timeRange) @@ -635,8 +633,8 @@ function discoverController($route, $scope, Promise) { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description, searchSessionId }); - inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); - $scope.searchSource.getSearchRequestBody().then((body) => { + inspectorRequest.stats(getRequestInspectorStats($scope.volatileSearchSource)); + $scope.volatileSearchSource.getSearchRequestBody().then((body) => { inspectorRequest.json(body); }); } @@ -693,9 +691,11 @@ function discoverController($route, $scope, Promise) { }; $scope.updateDataSource = () => { - const { indexPattern, searchSource, useNewFieldsApi } = $scope; + const { indexPattern, useNewFieldsApi } = $scope; const { columns, sort } = $scope.state; - updateSearchSource(searchSource, { + updateSearchSource({ + persistentSearchSource, + volatileSearchSource: $scope.volatileSearchSource, indexPattern, services, sort, @@ -731,12 +731,12 @@ function discoverController($route, $scope, Promise) { visStateAggs ); - $scope.searchSource.onRequestStart((searchSource, options) => { + $scope.volatileSearchSource.onRequestStart((searchSource, options) => { if (!$scope.opts.chartAggConfigs) return; return $scope.opts.chartAggConfigs.onSearchRequestStart(searchSource, options); }); - $scope.searchSource.setField('aggs', function () { + $scope.volatileSearchSource.setField('aggs', function () { if (!$scope.opts.chartAggConfigs) return; return $scope.opts.chartAggConfigs.toDsl(); }); diff --git a/src/plugins/discover/public/application/angular/discover_datagrid.html b/src/plugins/discover/public/application/angular/discover_datagrid.html index e59ebbb0fafd03..42218568a838db 100644 --- a/src/plugins/discover/public/application/angular/discover_datagrid.html +++ b/src/plugins/discover/public/application/angular/discover_datagrid.html @@ -17,7 +17,7 @@ reset-query="resetQuery" result-state="resultState" rows="rows" - search-source="searchSource" + search-source="volatileSearchSource" set-index-pattern="setIndexPattern" show-save-query="showSaveQuery" state="state" diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 501496494106af..a01f285b1a1506 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -13,7 +13,7 @@ reset-query="resetQuery" result-state="resultState" rows="rows" - search-source="searchSource" + search-source="volatileSearchSource" state="state" time-range="timeRange" top-nav-menu="topNavMenu" diff --git a/src/plugins/discover/public/application/angular/discover_state.test.ts b/src/plugins/discover/public/application/angular/discover_state.test.ts index d3b7ed53e421af..e7322a85886311 100644 --- a/src/plugins/discover/public/application/angular/discover_state.test.ts +++ b/src/plugins/discover/public/application/angular/discover_state.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { IUiSettingsClient } from 'kibana/public'; import { getState, GetStateReturn, @@ -14,11 +15,17 @@ import { import { createBrowserHistory, History } from 'history'; import { dataPluginMock } from '../../../../data/public/mocks'; import { SavedSearch } from '../../saved_searches'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; let history: History; let state: GetStateReturn; const getCurrentUrl = () => history.createHref(history.location); +const uiSettingsMock = { + get: (key: string) => + ((key === SEARCH_FIELDS_FROM_SOURCE ? true : ['_source']) as unknown) as T, +} as IUiSettingsClient; + describe('Test discover state', () => { beforeEach(async () => { history = createBrowserHistory(); @@ -26,6 +33,7 @@ describe('Test discover state', () => { state = getState({ getStateDefaults: () => ({ index: 'test' }), history, + uiSettings: uiSettingsMock, }); await state.replaceUrlAppState({}); await state.startSync(); @@ -81,6 +89,7 @@ describe('Test discover state with legacy migration', () => { state = getState({ getStateDefaults: () => ({ index: 'test' }), history, + uiSettings: uiSettingsMock, }); expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` Object { @@ -106,6 +115,7 @@ describe('createSearchSessionRestorationDataProvider', () => { data: mockDataPlugin, appStateContainer: getState({ history: createBrowserHistory(), + uiSettings: uiSettingsMock, }).appStateContainer, getSavedSearch: () => mockSavedSearch, }); diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index 93fc49b65cbc92..e7d5ed469525f7 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -9,7 +9,7 @@ import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; -import { NotificationsStart } from 'kibana/public'; +import { NotificationsStart, IUiSettingsClient } from 'kibana/public'; import { createKbnUrlStateStorage, createStateContainer, @@ -30,6 +30,7 @@ import { migrateLegacyQuery } from '../helpers/migrate_legacy_query'; import { DiscoverGridSettings } from '../components/discover_grid/types'; import { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from '../../url_generator'; import { SavedSearch } from '../../saved_searches'; +import { handleSourceColumnState } from './helpers'; export interface AppState { /** @@ -90,6 +91,11 @@ interface GetStateParams { * kbnUrlStateStorage will use it notifying about inner errors */ toasts?: NotificationsStart['toasts']; + + /** + * core ui settings service + */ + uiSettings: IUiSettingsClient; } export interface GetStateReturn { @@ -149,6 +155,7 @@ export function getState({ storeInSessionStorage = false, history, toasts, + uiSettings, }: GetStateParams): GetStateReturn { const defaultAppState = getStateDefaults ? getStateDefaults() : {}; const stateStorage = createKbnUrlStateStorage({ @@ -163,10 +170,14 @@ export function getState({ appStateFromUrl.query = migrateLegacyQuery(appStateFromUrl.query); } - let initialAppState = { - ...defaultAppState, - ...appStateFromUrl, - }; + let initialAppState = handleSourceColumnState( + { + ...defaultAppState, + ...appStateFromUrl, + }, + uiSettings + ); + // todo filter source depending on fields fetchinbg flag (if no columns remain and source fetching is enabled, use default columns) let previousAppState: AppState; const appStateContainer = createStateContainer(initialAppState); diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts index a4ab9e575e4217..3d4893268fdee8 100644 --- a/src/plugins/discover/public/application/angular/helpers/index.ts +++ b/src/plugins/discover/public/application/angular/helpers/index.ts @@ -8,3 +8,4 @@ export { buildPointSeriesData } from './point_series'; export { formatRow } from './row_formatter'; +export { handleSourceColumnState } from './state_helpers'; diff --git a/src/plugins/discover/public/application/angular/helpers/state_helpers.ts b/src/plugins/discover/public/application/angular/helpers/state_helpers.ts new file mode 100644 index 00000000000000..ec5009dcf48392 --- /dev/null +++ b/src/plugins/discover/public/application/angular/helpers/state_helpers.ts @@ -0,0 +1,42 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IUiSettingsClient } from 'src/core/public'; +import { SEARCH_FIELDS_FROM_SOURCE, DEFAULT_COLUMNS_SETTING } from '../../../../common'; + +/** + * Makes sure the current state is not referencing the source column when using the fields api + * @param state + * @param uiSettings + */ +export function handleSourceColumnState( + state: TState, + uiSettings: IUiSettingsClient +): TState { + if (!state.columns) { + return state; + } + const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + const defaultColumns = uiSettings.get(DEFAULT_COLUMNS_SETTING); + if (useNewFieldsApi) { + // if fields API is used, filter out the source column + return { + ...state, + columns: state.columns.filter((column) => column !== '_source'), + }; + } else if (state.columns.length === 0) { + // if _source fetching is used and there are no column, switch back to default columns + // this can happen if the fields API was previously used + return { + ...state, + columns: [...defaultColumns], + }; + } + + return state; +} diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 71650a4a38472e..1d183aa75cf3a5 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -206,6 +206,7 @@ export function Discover({ query={state.query} savedQuery={state.savedQuery} updateQuery={updateQuery} + searchSource={searchSource} />

diff --git a/src/plugins/discover/public/application/components/discover_grid/constants.ts b/src/plugins/discover/public/application/components/discover_grid/constants.ts index 03e5740793396e..015d0b65246f2e 100644 --- a/src/plugins/discover/public/application/components/discover_grid/constants.ts +++ b/src/plugins/discover/public/application/components/discover_grid/constants.ts @@ -8,7 +8,6 @@ // data types export const kibanaJSON = 'kibana-json'; -export const geoPoint = 'geo-point'; export const gridStyle = { border: 'all', fontSize: 's', diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index a4e59a50a2f6c7..fcaea63f2a77c4 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -315,7 +315,8 @@ export const DiscoverGrid = ({ { - return {children}; - }, [kibanaJSON]: ({ children }) => { return ( diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx index 45f30a9d26f935..cfcdbec475eda4 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -7,7 +7,6 @@ */ import React, { Fragment, useContext, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; import themeLight from '@elastic/eui/dist/eui_theme_light.json'; import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; @@ -83,21 +82,6 @@ export const getRenderCellValueFn = ( return {JSON.stringify(rowFlattened[columnId])}; } - if (field?.type === 'geo_point' && rowFlattened && rowFlattened[columnId]) { - const valueFormatted = rowFlattened[columnId] as { lat: number; lon: number }; - return ( -
- {i18n.translate('discover.latitudeAndLongitude', { - defaultMessage: 'Lat: {lat} Lon: {lon}', - values: { - lat: valueFormatted?.lat, - lon: valueFormatted?.lon, - }, - })} -
- ); - } - const valueFormatted = indexPattern.formatField(row, columnId); if (typeof valueFormatted === 'undefined') { return -; diff --git a/src/plugins/discover/public/application/components/discover_topnav.test.tsx b/src/plugins/discover/public/application/components/discover_topnav.test.tsx index 50c9bcfb5d2557..891dc63c92c7c6 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.test.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.test.tsx @@ -20,7 +20,7 @@ import { SavedObject } from '../../../../../core/types'; import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav'; import { RequestAdapter } from '../../../../inspector/common/adapters/request'; import { TopNavMenu } from '../../../../navigation/public'; -import { Query } from '../../../../data/common'; +import { ISearchSource, Query } from '../../../../data/common'; import { DiscoverSearchSessionManager } from '../angular/discover_search_session'; import { Subject } from 'rxjs'; @@ -61,6 +61,7 @@ function getProps(): DiscoverTopNavProps { savedQuery: '', updateQuery: jest.fn(), onOpenInspector: jest.fn(), + searchSource: {} as ISearchSource, }; } diff --git a/src/plugins/discover/public/application/components/discover_topnav.tsx b/src/plugins/discover/public/application/components/discover_topnav.tsx index fd2aba22aa41db..ee59ee13583bdd 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.tsx @@ -10,7 +10,7 @@ import { DiscoverProps } from './types'; import { getTopNavLinks } from './top_nav/get_top_nav_links'; import { Query, TimeRange } from '../../../../data/common/query'; -export type DiscoverTopNavProps = Pick & { +export type DiscoverTopNavProps = Pick & { onOpenInspector: () => void; query?: Query; savedQuery?: string; @@ -24,6 +24,7 @@ export const DiscoverTopNav = ({ query, savedQuery, updateQuery, + searchSource, }: DiscoverTopNavProps) => { const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]); const { TopNavMenu } = opts.services.navigation.ui; @@ -38,8 +39,9 @@ export const DiscoverTopNav = ({ services: opts.services, state: opts.stateContainer, onOpenInspector, + searchSource, }), - [indexPattern, opts, onOpenInspector] + [indexPattern, opts, onOpenInspector, searchSource] ); const updateSavedQueryId = (newSavedQueryId: string | undefined) => { diff --git a/src/plugins/discover/public/application/components/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx index 623d30d5f53ecf..deaaa1853ae9da 100644 --- a/src/plugins/discover/public/application/components/doc/doc.test.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx @@ -13,6 +13,7 @@ import { mountWithIntl } from '@kbn/test/jest'; import { ReactWrapper } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; +import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../../../common'; const mockSearchApi = jest.fn(); @@ -36,6 +37,13 @@ jest.mock('../../../kibana_services', () => { }, }, }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } + }, + }, }), getDocViewsRegistry: () => ({ addDocView(view: any) { diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx index 6cb0bf1288d3a8..dc0b6416a7f576 100644 --- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx +++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx @@ -10,6 +10,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search'; import { DocProps } from './doc'; import { Observable } from 'rxjs'; +import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../../../common'; const mockSearchResult = new Observable(); @@ -22,19 +23,55 @@ jest.mock('../../../kibana_services', () => ({ }), }, }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } + }, + }, }), })); describe('Test of helper / hook', () => { - test('buildSearchBody', () => { + test('buildSearchBody given useNewFieldsApi is false', () => { const indexPattern = { getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }), } as any; - const actual = buildSearchBody('1', indexPattern); + const actual = buildSearchBody('1', indexPattern, false); expect(actual).toMatchInlineSnapshot(` Object { "_source": true, "docvalue_fields": Array [], + "fields": undefined, + "query": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + "script_fields": Array [], + "stored_fields": Array [], + } + `); + }); + + test('buildSearchBody useNewFieldsApi is true', () => { + const indexPattern = { + getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }), + } as any; + const actual = buildSearchBody('1', indexPattern, true); + expect(actual).toMatchInlineSnapshot(` + Object { + "_source": false, + "docvalue_fields": Array [], + "fields": Array [ + Object { + "field": "*", + "include_unmapped": "true", + }, + ], "query": Object { "ids": Object { "values": Array [ diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts index 2a63a62650ca90..703a1e557e801a 100644 --- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts +++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { IndexPattern, getServices } from '../../../kibana_services'; import { DocProps } from './doc'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../../common'; export enum ElasticRequestState { Loading, @@ -23,7 +24,11 @@ export enum ElasticRequestState { * helper function to build a query body for Elasticsearch * https://www.elastic.co/guide/en/elasticsearch/reference/current//query-dsl-ids-query.html */ -export function buildSearchBody(id: string, indexPattern: IndexPattern): Record { +export function buildSearchBody( + id: string, + indexPattern: IndexPattern, + useNewFieldsApi: boolean +): Record { const computedFields = indexPattern.getComputedFields(); return { @@ -33,7 +38,8 @@ export function buildSearchBody(id: string, indexPattern: IndexPattern): Record< }, }, stored_fields: computedFields.storedFields, - _source: true, + _source: !useNewFieldsApi, + fields: useNewFieldsApi ? [{ field: '*', include_unmapped: 'true' }] : undefined, script_fields: computedFields.scriptFields, docvalue_fields: computedFields.docvalueFields, }; @@ -51,6 +57,8 @@ export function useEsDocSearch({ const [indexPattern, setIndexPattern] = useState(null); const [status, setStatus] = useState(ElasticRequestState.Loading); const [hit, setHit] = useState(null); + const { data, uiSettings } = useMemo(() => getServices(), []); + const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); useEffect(() => { async function requestData() { @@ -58,11 +66,11 @@ export function useEsDocSearch({ const indexPatternEntity = await indexPatternService.get(indexPatternId); setIndexPattern(indexPatternEntity); - const { rawResponse } = await getServices() - .data.search.search({ + const { rawResponse } = await data.search + .search({ params: { index, - body: buildSearchBody(id, indexPatternEntity), + body: buildSearchBody(id, indexPatternEntity, useNewFieldsApi), }, }) .toPromise(); @@ -86,6 +94,6 @@ export function useEsDocSearch({ } } requestData(); - }, [id, index, indexPatternId, indexPatternService]); + }, [id, index, indexPatternId, indexPatternService, data.search, useNewFieldsApi]); return [status, hit, indexPattern]; } diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx index 62e19b83b016e4..6afa7f89371f98 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx @@ -16,6 +16,11 @@ import { DocViewRenderProps } from '../../doc_views/doc_views_types'; jest.mock('../../../kibana_services', () => { let registry: any[] = []; return { + getServices: () => ({ + uiSettings: { + get: jest.fn(), + }, + }), getDocViewsRegistry: () => ({ addDocView(view: any) { registry.push(view); diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx index 571af94f293728..b9b068ce4bd073 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx @@ -7,20 +7,18 @@ */ import React from 'react'; -import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; -import { formatMsg, formatStack } from '../../../../../kibana_legacy/public'; +import { EuiErrorBoundary } from '@elastic/eui'; interface Props { error: Error | string; } -export function DocViewerError({ error }: Props) { - const errMsg = formatMsg(error); - const errStack = typeof error === 'object' ? formatStack(error) : ''; +const DocViewerErrorWrapper = ({ error }: Props) => { + throw error; +}; - return ( - - {errStack && {errStack}} - - ); -} +export const DocViewerError = ({ error }: Props) => ( + + + +); diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx index 1da75b45239105..25454a3bad38ab 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx @@ -11,6 +11,8 @@ import { I18nProvider } from '@kbn/i18n/react'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; import { DocViewRenderFn, DocViewRenderProps } from '../../doc_views/doc_views_types'; +import { getServices } from '../../../kibana_services'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; interface Props { component?: React.ComponentType; @@ -72,7 +74,9 @@ export class DocViewerTab extends React.Component { const Component = component as any; return ( - + + + ); } diff --git a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap b/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap deleted file mode 100644 index d6f48a9b3c7745..00000000000000 --- a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`returns the \`JsonCodeEditor\` component 1`] = ` - - { - "_index": "test", - "_type": "doc", - "_id": "foo", - "_score": 1, - "_source": { - "test": 123 - } -} - -`; diff --git a/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx b/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx deleted file mode 100644 index 7dab2b199b018f..00000000000000 --- a/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx +++ /dev/null @@ -1,23 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiCodeBlock } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DocViewRenderProps } from '../../doc_views/doc_views_types'; - -export function JsonCodeBlock({ hit }: DocViewRenderProps) { - const label = i18n.translate('discover.docViews.json.codeEditorAriaLabel', { - defaultMessage: 'Read only JSON view of an elasticsearch document', - }); - return ( - - {JSON.stringify(hit, null, 2)} - - ); -} diff --git a/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap b/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap new file mode 100644 index 00000000000000..4f27158eee04f1 --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`returns the \`JsonCodeEditor\` component 1`] = ` + + + +
+ + + +
+
+ + + +
+`; diff --git a/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss new file mode 100644 index 00000000000000..5521df5b363acd --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss @@ -0,0 +1,3 @@ +.dscJsonCodeEditor { + width: 100% +} diff --git a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx similarity index 53% rename from src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx rename to src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx index dd56a1077f1ac6..4ccb3010d5a2b9 100644 --- a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx @@ -8,17 +8,15 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { JsonCodeBlock } from './json_code_block'; -import { IndexPattern } from '../../../../../data/public'; +import { JsonCodeEditor } from './json_code_editor'; it('returns the `JsonCodeEditor` component', () => { - const props = { - hit: { _index: 'test', _type: 'doc', _id: 'foo', _score: 1, _source: { test: 123 } }, - columns: [], - indexPattern: {} as IndexPattern, - filter: jest.fn(), - onAddColumn: jest.fn(), - onRemoveColumn: jest.fn(), + const value = { + _index: 'test', + _type: 'doc', + _id: 'foo', + _score: 1, + _source: { test: 123 }, }; - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); diff --git a/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx new file mode 100644 index 00000000000000..85d6aad7552502 --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx @@ -0,0 +1,84 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './json_code_editor.scss'; + +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { monaco, XJsonLang } from '@kbn/monaco'; +import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { CodeEditor } from '../../../../../kibana_react/public'; +import { DocViewRenderProps } from '../../../application/doc_views/doc_views_types'; + +const codeEditorAriaLabel = i18n.translate('discover.json.codeEditorAriaLabel', { + defaultMessage: 'Read only JSON view of an elasticsearch document', +}); +const copyToClipboardLabel = i18n.translate('discover.json.copyToClipboardLabel', { + defaultMessage: 'Copy to clipboard', +}); + +export const JsonCodeEditor = ({ hit }: DocViewRenderProps) => { + const jsonValue = JSON.stringify(hit, null, 2); + + // setting editor height based on lines height and count to stretch and fit its content + const setEditorCalculatedHeight = useCallback((editor) => { + const editorElement = editor.getDomNode(); + + if (!editorElement) { + return; + } + + const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); + const lineCount = editor.getModel()?.getLineCount() || 1; + const height = editor.getTopForLineNumber(lineCount + 1) + lineHeight; + + editorElement.style.height = `${height}px`; + editor.layout(); + }, []); + + return ( + + + +
+ + {(copy) => ( + + {copyToClipboardLabel} + + )} + +
+
+ + {}} + editorDidMount={setEditorCalculatedHeight} + aria-label={codeEditorAriaLabel} + options={{ + automaticLayout: true, + fontSize: 12, + minimap: { + enabled: false, + }, + overviewRulerBorder: false, + readOnly: true, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + /> + +
+ ); +}; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index 89980f7fd0f54a..9792e98ba84c7d 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts @@ -47,6 +47,7 @@ const fieldCounts = { category: 1, currency: 1, customer_birth_date: 1, + unknown_field: 1, }; describe('group_fields', function () { @@ -232,7 +233,7 @@ describe('group_fields', function () { const actual = groupFields( fieldsWithUnmappedField as IndexPatternField[], - ['customer_birth_date', 'currency', 'unknown'], + ['customer_birth_date', 'currency'], 5, fieldCounts, fieldFilterState, @@ -241,4 +242,30 @@ describe('group_fields', function () { ); expect(actual.unpopular).toEqual([]); }); + + it('includes unmapped fields when reading from source', function () { + const fieldFilterState = getDefaultFieldFilter(); + const fieldsWithUnmappedField = [...fields]; + fieldsWithUnmappedField.push({ + name: 'unknown_field', + type: 'unknown', + esTypes: ['unknown'], + count: 0, + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }); + + const actual = groupFields( + fieldsWithUnmappedField as IndexPatternField[], + ['customer_birth_date', 'currency'], + 5, + fieldCounts, + fieldFilterState, + false, + undefined + ); + expect(actual.unpopular.map((field) => field.name)).toEqual(['unknown_field']); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx index c7242a8518b553..eefb96b78aac66 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx @@ -64,7 +64,9 @@ export function groupFields( } else if (field.type !== '_source') { // do not show unmapped fields unless explicitly specified // do not add subfields to this list - if ((field.type !== 'unknown' || showUnmappedFields) && !isSubfield) { + if (useNewFieldsApi && (field.type !== 'unknown' || showUnmappedFields) && !isSubfield) { + result.unpopular.push(field); + } else if (!useNewFieldsApi) { result.unpopular.push(field); } } diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts index 89cb60700074d1..30edb102c420aa 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ISearchSource } from 'src/plugins/data/public'; import { getTopNavLinks } from './get_top_nav_links'; import { inspectorPluginMock } from '../../../../../inspector/public/mocks'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; @@ -33,6 +34,7 @@ test('getTopNavLinks result', () => { savedSearch: savedSearchMock, services, state, + searchSource: {} as ISearchSource, }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts index 513508c478aa95..a1215836f9c5fc 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts @@ -15,7 +15,7 @@ import { Adapters } from '../../../../../inspector/common/adapters'; import { SavedSearch } from '../../../saved_searches'; import { onSaveSearch } from './on_save_search'; import { GetStateReturn } from '../../angular/discover_state'; -import { IndexPattern } from '../../../kibana_services'; +import { IndexPattern, ISearchSource } from '../../../kibana_services'; /** * Helper function to build the top nav links @@ -29,6 +29,7 @@ export const getTopNavLinks = ({ services, state, onOpenInspector, + searchSource, }: { getFieldCounts: () => Promise>; indexPattern: IndexPattern; @@ -38,6 +39,7 @@ export const getTopNavLinks = ({ services: DiscoverServices; state: GetStateReturn; onOpenInspector: () => void; + searchSource: ISearchSource; }) => { const newSearch = { id: 'new', @@ -93,7 +95,7 @@ export const getTopNavLinks = ({ return; } const sharingData = await getSharingData( - savedSearch.searchSource, + searchSource, state.appStateContainer.getState(), services.uiSettings, getFieldCounts diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.ts index 85acd575138f71..31de1f2f6ed66a 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.ts @@ -9,7 +9,7 @@ import { Capabilities, IUiSettingsClient } from 'kibana/public'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; import { getSortForSearchSource } from '../angular/doc_table'; -import { SearchSource } from '../../../../data/common'; +import { ISearchSource } from '../../../../data/common'; import { AppState } from '../angular/discover_state'; import { SortOrder } from '../../saved_searches/types'; @@ -39,7 +39,7 @@ const getSharingDataFields = async ( * Preparing data to share the current state as link or CSV/Report */ export async function getSharingData( - currentSearchSource: SearchSource, + currentSearchSource: ISearchSource, state: AppState, config: IUiSettingsClient, getFieldCounts: () => Promise> diff --git a/src/plugins/discover/public/application/helpers/persist_saved_search.ts b/src/plugins/discover/public/application/helpers/persist_saved_search.ts index 06e90c93bc77c9..2e4ab90ee58e54 100644 --- a/src/plugins/discover/public/application/helpers/persist_saved_search.ts +++ b/src/plugins/discover/public/application/helpers/persist_saved_search.ts @@ -35,7 +35,8 @@ export async function persistSavedSearch( state: AppState; } ) { - updateSearchSource(savedSearch.searchSource, { + updateSearchSource({ + persistentSearchSource: savedSearch.searchSource, indexPattern, services, sort: state.sort as SortOrder[], diff --git a/src/plugins/discover/public/application/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/helpers/update_search_source.test.ts index 51586a6bccc234..97e2de3541d354 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.test.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.test.ts @@ -17,9 +17,12 @@ import { SortOrder } from '../../saved_searches/types'; describe('updateSearchSource', () => { test('updates a given search source', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -36,15 +39,18 @@ describe('updateSearchSource', () => { columns: [], useNewFieldsApi: false, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toBe(undefined); }); test('updates a given search source with the usage of the new fields api', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -61,16 +67,19 @@ describe('updateSearchSource', () => { columns: [], useNewFieldsApi: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([{ field: '*' }]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); test('requests unmapped fields when the flag is provided, using the new fields api', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -88,16 +97,21 @@ describe('updateSearchSource', () => { useNewFieldsApi: true, showUnmappedFields: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([ + { field: '*', include_unmapped: 'true' }, + ]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); test('updates a given search source when showUnmappedFields option is set to true', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -115,9 +129,11 @@ describe('updateSearchSource', () => { useNewFieldsApi: true, showUnmappedFields: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([ + { field: '*', include_unmapped: 'true' }, + ]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); }); diff --git a/src/plugins/discover/public/application/helpers/update_search_source.ts b/src/plugins/discover/public/application/helpers/update_search_source.ts index 55d2b05a29b690..ba5ac0e8227965 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.ts @@ -15,24 +15,25 @@ import { DiscoverServices } from '../../build_services'; /** * Helper function to update the given searchSource before fetching/sharing/persisting */ -export function updateSearchSource( - searchSource: ISearchSource, - { - indexPattern, - services, - sort, - columns, - useNewFieldsApi, - showUnmappedFields, - }: { - indexPattern: IndexPattern; - services: DiscoverServices; - sort: SortOrder[]; - columns: string[]; - useNewFieldsApi: boolean; - showUnmappedFields?: boolean; - } -) { +export function updateSearchSource({ + indexPattern, + services, + sort, + columns, + useNewFieldsApi, + showUnmappedFields, + persistentSearchSource, + volatileSearchSource, +}: { + indexPattern: IndexPattern; + services: DiscoverServices; + sort: SortOrder[]; + columns: string[]; + useNewFieldsApi: boolean; + showUnmappedFields?: boolean; + persistentSearchSource: ISearchSource; + volatileSearchSource?: ISearchSource; +}) { const { uiSettings, data } = services; const usedSort = getSortForSearchSource( sort, @@ -40,23 +41,32 @@ export function updateSearchSource( uiSettings.get(SORT_DEFAULT_ORDER_SETTING) ); - searchSource + persistentSearchSource .setField('index', indexPattern) - .setField('size', uiSettings.get(SAMPLE_SIZE_SETTING)) - .setField('sort', usedSort) .setField('query', data.query.queryString.getQuery() || null) .setField('filter', data.query.filterManager.getFilters()); - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - const fields: Record = { field: '*' }; - if (showUnmappedFields) { - fields.include_unmapped = 'true'; + + if (volatileSearchSource) { + volatileSearchSource + .setField('size', uiSettings.get(SAMPLE_SIZE_SETTING)) + .setField('sort', usedSort) + .setField('highlightAll', true) + .setField('version', true) + // Even when searching rollups, we want to use the default strategy so that we get back a + // document-like response. + .setPreferredSearchStrategyId('default'); + + if (useNewFieldsApi) { + volatileSearchSource.removeField('fieldsFromSource'); + const fields: Record = { field: '*' }; + if (showUnmappedFields) { + fields.include_unmapped = 'true'; + } + volatileSearchSource.setField('fields', [fields]); + } else { + volatileSearchSource.removeField('fields'); + const fieldNames = indexPattern.fields.map((field) => field.name); + volatileSearchSource.setField('fieldsFromSource', fieldNames); } - searchSource.setField('fields', [fields]); - } else { - searchSource.removeField('fields'); - const fieldNames = indexPattern.fields.map((field) => field.name); - searchSource.setField('fieldsFromSource', fieldNames); } - return searchSource; } diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index c53dfaff24112f..47161c2b8298e1 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -36,7 +36,7 @@ import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewTable } from './application/components/table/table'; -import { JsonCodeBlock } from './application/components/json_code_block/json_code_block'; +import { JsonCodeEditor } from './application/components/json_code_editor/json_code_editor'; import { setDocViewsRegistry, setUrlTracker, @@ -187,7 +187,7 @@ export class DiscoverPlugin defaultMessage: 'JSON', }), order: 20, - component: JsonCodeBlock, + component: JsonCodeEditor, }); const { diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts index b645b62c863d5d..4a45cff0b96049 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts @@ -38,5 +38,5 @@ export const handleEsError = ({ }); } // Case: default - return response.internalError({ body: error }); + throw error; }; diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index 7c00a46602e26e..a20c3e350222f3 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -136,8 +136,7 @@ export function createInstallRoute( (counts as any)[index] = count; } catch (err) { const errMsg = `sample_data install errors while loading data. Error: ${err}`; - logger.warn(errMsg); - return res.internalError({ body: errMsg }); + throw new Error(errMsg); } } @@ -157,8 +156,7 @@ export function createInstallRoute( ); } catch (err) { const errMsg = `bulkCreate failed, error: ${err.message}`; - logger.warn(errMsg); - return res.internalError({ body: errMsg }); + throw new Error(errMsg); } const errors = createResults.saved_objects.filter((savedObjectCreateResult) => { return Boolean(savedObjectCreateResult.error); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts index 35592d83cf822a..566c9428901502 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts @@ -77,5 +77,5 @@ export async function getKibana( kibanaRequest: KibanaRequest | undefined // intentionally `| undefined` to enforce providing the parameter ): Promise { const usage = await usageCollection.bulkFetch(asInternalUser, soClient, kibanaRequest); - return usageCollection.toObject(usage); + return usageCollection.toObject(usage); } diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts index 9338af611758ea..32a58a6657eec2 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -211,46 +211,47 @@ export class CollectorSet { ); }; - // convert an array of fetched stats results into key/object - public toObject = (statsData: Array<{ type: string; result: T }> = []) => { - return statsData.reduce((accumulatedStats, { type, result }) => { - return { - ...accumulatedStats, - [type]: result, - }; - }, {} as Result); - }; - - // rename fields to use api conventions - public toApiFieldNames = (apiData: any): any => { - const getValueOrRecurse = (value: any) => { - if (value == null || typeof value !== 'object') { - return value; - } else { - return this.toApiFieldNames(value); // recurse - } - }; + /** + * Convert an array of fetched stats results into key/object + * @param statsData Array of fetched stats results + */ + public toObject, T = unknown>( + statsData: Array<{ type: string; result: T }> = [] + ): Result { + return Object.fromEntries(statsData.map(({ type, result }) => [type, result])) as Result; + } + /** + * Rename fields to use API conventions + * @param apiData Data to be normalized + */ + public toApiFieldNames( + apiData: Record | unknown[] + ): Record | unknown[] { // handle array and return early, or return a reduced object - if (Array.isArray(apiData)) { - return apiData.map(getValueOrRecurse); + return apiData.map((value) => this.getValueOrRecurse(value)); } - return Object.keys(apiData).reduce((accum, field) => { - const value = apiData[field]; - let newName = field; - newName = snakeCase(newName); - newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m - newName = newName.replace('_in_bytes', '_bytes'); - newName = newName.replace('_in_millis', '_ms'); + return Object.fromEntries( + Object.entries(apiData).map(([field, value]) => { + let newName = field; + newName = snakeCase(newName); + newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m + newName = newName.replace('_in_bytes', '_bytes'); + newName = newName.replace('_in_millis', '_ms'); - return { - ...accum, - [newName]: getValueOrRecurse(value), - }; - }, {}); - }; + return [newName, this.getValueOrRecurse(value)]; + }) + ); + } + + private getValueOrRecurse(value: unknown) { + if (Array.isArray(value) || (typeof value === 'object' && value !== null)) { + return this.toApiFieldNames(value as Record | unknown[]); // recurse + } + return value; + } private makeCollectorSetFromArray = (collectors: AnyCollector[]) => { return new CollectorSet({ diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index bae25d6f918e37..b3ab3c61c15d89 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -77,46 +77,32 @@ export function runRoute( }, }, router.handleLegacyErrors(async (context, request, response) => { - try { - const [, { data }] = await core.getStartServices(); - const uiSettings = await context.core.uiSettings.client.getAll(); - const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser - ); + const [, { data }] = await core.getStartServices(); + const uiSettings = await context.core.uiSettings.client.getAll(); + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); - const tlConfig = getTlConfig({ - context, - request, - settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. - getFunction, - getIndexPatternsService: () => indexPatternsService, - getStartServices: core.getStartServices, - allowedGraphiteUrls: configManager.getGraphiteUrls(), - esShardTimeout: configManager.getEsShardTimeout(), - }); - const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); + const tlConfig = getTlConfig({ + context, + request, + settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. + getFunction, + getIndexPatternsService: () => indexPatternsService, + getStartServices: core.getStartServices, + allowedGraphiteUrls: configManager.getGraphiteUrls(), + esShardTimeout: configManager.getEsShardTimeout(), + }); + const chainRunner = chainRunnerFn(tlConfig); + const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); - return response.ok({ - body: { - sheet, - stats: chainRunner.getStats(), - }, - }); - } catch (err) { - logger.error(`${err.toString()}: ${err.stack}`); - // TODO Maybe we should just replace everywhere we throw with Boom? Probably. - if (err.isBoom) { - throw err; - } else { - return response.internalError({ - body: { - message: err.toString(), - }, - }); - } - } + return response.ok({ + body: { + sheet, + stats: chainRunner.getStats(), + }, + }); }) ); } diff --git a/src/plugins/vis_type_timeseries/common/metric_types.ts b/src/plugins/vis_type_timeseries/common/metric_types.ts index 52b31b67743715..17352f0f9da258 100644 --- a/src/plugins/vis_type_timeseries/common/metric_types.ts +++ b/src/plugins/vis_type_timeseries/common/metric_types.ts @@ -20,6 +20,8 @@ export enum METRIC_TYPES { VALUE_COUNT = 'value_count', AVERAGE = 'avg', SUM = 'sum', + MIN = 'min', + MAX = 'max', } // We should probably use BUCKET_TYPES from data plugin in future. diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx index d82a16f1186219..579205ae048444 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx @@ -87,6 +87,14 @@ describe('TSVB AggSelect', () => { "label": "Count", "value": "count", }, + Object { + "label": "Max", + "value": "max", + }, + Object { + "label": "Min", + "value": "min", + }, Object { "label": "Sum", "value": "sum", diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx index 0b8fad4d780f64..00d088025bf253 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx @@ -225,7 +225,7 @@ const FILTER_RATIO_AGGS = [ 'value_count', ]; -const HISTOGRAM_AGGS = ['avg', 'count', 'sum', 'value_count']; +const HISTOGRAM_AGGS = ['avg', 'count', 'sum', 'min', 'max', 'value_count']; const allAggOptions = [...metricAggs, ...pipelineAggs, ...siblingAggs, ...specialAggs]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js index dd85e2490d70a7..5648a8d3e7133a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js @@ -51,26 +51,13 @@ describe('TSVB Filter Ratio', () => { field: 'histogram_value', }; const wrapper = setup(metric); - expect(wrapper.find(EuiComboBox).at(1).props().options).toMatchInlineSnapshot(` - Array [ - Object { - "label": "Average", - "value": "avg", - }, - Object { - "label": "Count", - "value": "count", - }, - Object { - "label": "Sum", - "value": "sum", - }, - Object { - "label": "Value Count", - "value": "value_count", - }, - ] - `); + expect( + wrapper + .find(EuiComboBox) + .at(1) + .props() + .options.map(({ value }) => value) + ).toEqual(['avg', 'count', 'max', 'min', 'sum', 'value_count']); }); const shouldNotHaveHistogramField = (agg) => { it(`should not have histogram fields for ${agg}`, () => { @@ -80,23 +67,19 @@ describe('TSVB Filter Ratio', () => { field: '', }; const wrapper = setup(metric); - expect(wrapper.find(EuiComboBox).at(2).props().options).toMatchInlineSnapshot(` - Array [ - Object { - "label": "number", - "options": Array [ - Object { - "label": "system.cpu.user.pct", - "value": "system.cpu.user.pct", - }, - ], - }, - ] - `); + expect(wrapper.find(EuiComboBox).at(2).props().options).toEqual([ + { + label: 'number', + options: [ + { + label: 'system.cpu.user.pct', + value: 'system.cpu.user.pct', + }, + ], + }, + ]); }); }; - shouldNotHaveHistogramField('max'); - shouldNotHaveHistogramField('min'); shouldNotHaveHistogramField('positive_rate'); it(`should not have histogram fields for cardinality`, () => { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js index 5a5f37db2b7b5e..c536856327f283 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js @@ -65,6 +65,8 @@ describe('Histogram Types', () => { }; shouldHaveHistogramSupport('avg'); shouldHaveHistogramSupport('sum'); + shouldHaveHistogramSupport('min'); + shouldHaveHistogramSupport('max'); shouldHaveHistogramSupport('value_count'); shouldHaveHistogramSupport('percentile'); shouldHaveHistogramSupport('percentile_rank'); @@ -81,8 +83,6 @@ describe('Histogram Types', () => { ); }; shouldNotHaveHistogramSupport('cardinality'); - shouldNotHaveHistogramSupport('max'); - shouldNotHaveHistogramSupport('min'); shouldNotHaveHistogramSupport('variance'); shouldNotHaveHistogramSupport('sum_of_squares'); shouldNotHaveHistogramSupport('std_deviation'); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js index ba3b330ed8eef2..61226e4e8dcbe5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js @@ -17,6 +17,8 @@ export function getSupportedFieldsByMetricType(type) { return Object.values(KBN_FIELD_TYPES); case METRIC_TYPES.AVERAGE: case METRIC_TYPES.SUM: + case METRIC_TYPES.MIN: + case METRIC_TYPES.MAX: return [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM]; default: return [KBN_FIELD_TYPES.NUMBER]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js index 10258effbb8836..c009146abb7bd1 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js @@ -26,11 +26,11 @@ describe('getSupportedFieldsByMetricType', () => { shouldSupportAllFieldTypes('value_count'); shouldHaveHistogramAndNumbers('avg'); shouldHaveHistogramAndNumbers('sum'); + shouldHaveHistogramAndNumbers('min'); + shouldHaveHistogramAndNumbers('max'); shouldHaveOnlyNumbers('positive_rate'); shouldHaveOnlyNumbers('std_deviation'); - shouldHaveOnlyNumbers('max'); - shouldHaveOnlyNumbers('min'); it(`should return everything but histogram for cardinality`, () => { expect(getSupportedFieldsByMetricType('cardinality')).not.toContain('histogram'); diff --git a/src/plugins/vis_type_timeseries/server/routes/vis.ts b/src/plugins/vis_type_timeseries/server/routes/vis.ts index 7c314228dad246..15890011d75bb9 100644 --- a/src/plugins/vis_type_timeseries/server/routes/vis.ts +++ b/src/plugins/vis_type_timeseries/server/routes/vis.ts @@ -42,18 +42,12 @@ export const visDataRoutes = (router: VisTypeTimeseriesRouter, framework: Framew ); } - try { - const results = await getVisData( - requestContext, - request as KibanaRequest<{}, {}, GetVisDataOptions>, - framework - ); - return response.ok({ body: results }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const results = await getVisData( + requestContext, + request as KibanaRequest<{}, {}, GetVisDataOptions>, + framework + ); + return response.ok({ body: results }); } ); }; diff --git a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx index 0c1ab262755a73..c9ed82fcf58e55 100644 --- a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx +++ b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx @@ -27,9 +27,6 @@ interface TooltipData { value: string; } -// TODO: replace when exported from elastic/charts -const DEFAULT_SINGLE_PANEL_SM_VALUE = '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__'; - export const getTooltipData = ( aspects: Aspects, header: TooltipValue | null, @@ -81,7 +78,7 @@ export const getTooltipData = ( if ( aspects.splitColumn && valueSeries.smHorizontalAccessorValue !== undefined && - valueSeries.smHorizontalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE + valueSeries.smHorizontalAccessorValue !== undefined ) { data.push({ label: aspects.splitColumn.title, @@ -92,7 +89,7 @@ export const getTooltipData = ( if ( aspects.splitRow && valueSeries.smVerticalAccessorValue !== undefined && - valueSeries.smVerticalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE + valueSeries.smVerticalAccessorValue !== undefined ) { data.push({ label: aspects.splitRow.title, diff --git a/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx b/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx index 9f557806cf1427..5c28ca77da0c4a 100644 --- a/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx +++ b/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx @@ -20,7 +20,7 @@ export const getLegendActions = ( onFilter: (data: ClickTriggerEvent, negate?: any) => void, getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName ): LegendAction => { - return ({ series: xySeries }) => { + return ({ series: [xySeries] }) => { const [popoverOpen, setPopoverOpen] = useState(false); const [isfilterable, setIsfilterable] = useState(false); const series = xySeries as XYChartSeriesIdentifier; diff --git a/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx b/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx index 80e7e12adf799d..5028bc379c375d 100644 --- a/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx +++ b/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx @@ -36,7 +36,7 @@ export const useColorPicker = ( getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName ): LegendColorPicker => useMemo( - () => ({ anchor, color, onClose, onChange, seriesIdentifier }) => { + () => ({ anchor, color, onClose, onChange, seriesIdentifiers: [seriesIdentifier] }) => { const seriesName = getSeriesName(seriesIdentifier as XYChartSeriesIdentifier); const handlChange = (newColor: string | null, event: BaseSyntheticEvent) => { if (!seriesName) { diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index c7ba5021196ff7..ab398101bac9d6 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -157,17 +157,12 @@ const VisComponent = (props: VisComponentProps) => { ( visData: Datatable, xAccessor: Accessor | AccessorFn, - splitSeriesAccessors: Array, - splitChartAccessor?: Accessor | AccessorFn + splitSeriesAccessors: Array ) => { const splitSeriesAccessorFnMap = getSplitSeriesAccessorFnMap(splitSeriesAccessors); return (series: XYChartSeriesIdentifier): ClickTriggerEvent | null => { if (xAccessor !== null) { - return getFilterFromSeriesFn(visData)( - series, - splitSeriesAccessorFnMap, - splitChartAccessor - ); + return getFilterFromSeriesFn(visData)(series, splitSeriesAccessorFnMap); } return null; @@ -373,12 +368,7 @@ const VisComponent = (props: VisComponentProps) => { config.aspects.series && (config.aspects.series?.length ?? 0) > 0 ? getLegendActions( canFilter, - getFilterEventData( - visData, - xAccessor, - splitSeriesAccessors, - splitChartColumnAccessor ?? splitChartRowAccessor - ), + getFilterEventData(visData, xAccessor, splitSeriesAccessors), handleFilterAction, getSeriesName ) diff --git a/test/common/fixtures/plugins/newsfeed/server/plugin.ts b/test/common/fixtures/plugins/newsfeed/server/plugin.ts index 732bc6c4732436..49ffa464efac92 100644 --- a/test/common/fixtures/plugins/newsfeed/server/plugin.ts +++ b/test/common/fixtures/plugins/newsfeed/server/plugin.ts @@ -34,7 +34,7 @@ export class NewsFeedSimulatorPlugin implements Plugin { options: { authRequired: false }, }, (context, req, res) => { - return res.internalError({ body: new Error('Internal server error') }); + throw new Error('Internal server error'); } ); } diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index 076a53a618c46a..2893102367b04b 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -77,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { '/app/discover?_t=1453775307251#' + '/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time' + ":(from:'2015-09-19T06:31:44.000Z',to:'2015-09" + - "-23T18:31:44.000Z'))&_a=(columns:!(_source),filters:!(),index:'logstash-" + + "-23T18:31:44.000Z'))&_a=(columns:!(),filters:!(),index:'logstash-" + "*',interval:auto,query:(language:kuery,query:'')" + ",sort:!(!('@timestamp',desc)))"; const actualUrl = await PageObjects.share.getSharedUrl(); diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index e2a056359b48e2..a3daaf86294939 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -23,8 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); - // FLAKY: https://github.com/elastic/kibana/issues/89478 - describe.skip('import objects', function describeIndexTests() { + describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { await esArchiver.load('management'); diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 81569c5bfc498a..89889088bd73ba 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -55,8 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await button.click(); }; - // Flaky: https://github.com/elastic/kibana/issues/68400 - describe.skip('saved objects edition page', () => { + describe('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index f731ffade6efc7..5bf99b4bf1136b 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -37,14 +37,21 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }; const writeCoverage = (coverageJson: string) => { - if (!Fs.existsSync(coverageDir)) { - Fs.mkdirSync(coverageDir, { recursive: true }); + // on CI we make hard link clone and run tests from kibana${process.env.CI_GROUP} root path + const re = new RegExp(`kibana${process.env.CI_GROUP}`, 'g'); + const dir = process.env.CI ? coverageDir.replace(re, 'kibana') : coverageDir; + + if (!Fs.existsSync(dir)) { + Fs.mkdirSync(dir, { recursive: true }); } + const id = coverageCounter++; const timestamp = Date.now(); - const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); + const path = resolve(dir, `${id}.${timestamp}.coverage.json`); log.info('writing coverage to', path); - Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); + + const jsonString = process.env.CI ? coverageJson.replace(re, 'kibana') : coverageJson; + Fs.writeFileSync(path, JSON.stringify(JSON.parse(jsonString), null, 2)); }; const browserConfig: BrowserConfig = { diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 47644256b05e7c..ae7abdaebba101 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -18,7 +18,7 @@ export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { const router = core.http.createRouter(); router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => { - if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' }); + if (!context.pluginA) throw new Error('pluginA is disabled'); const response = await context.pluginA.ping(); return res.ok({ body: `Pong via plugin A: ${response}` }); }); diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 4faf645975c778..58dcc9f52c0893 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -29,14 +29,6 @@ else echo " -> running tests from the clone folder" node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" --exclude-tag "skipCoverage" || true; - if [[ -d target/kibana-coverage/functional ]]; then - echo " -> replacing kibana${CI_GROUP} with kibana in json files" - sed -i "s|kibana${CI_GROUP}|kibana|g" target/kibana-coverage/functional/*.json - echo " -> copying coverage to the original folder" - mkdir -p ../kibana/target/kibana-coverage/functional - mv target/kibana-coverage/functional/* ../kibana/target/kibana-coverage/functional/ - fi - echo " -> moving junit output, silently fail in case of no report" mkdir -p ../kibana/target/junit mv target/junit/* ../kibana/target/junit/ || echo "copying junit failed" diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index a483f8378b8b41..3300ed2d0884f9 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -28,7 +28,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then ./test/scripts/checks/test_hardening.sh else echo " -> Running jest tests with coverage" - node scripts/jest --ci --verbose --maxWorkers=6 --coverage || true; + node scripts/jest --ci --verbose --maxWorkers=8 --coverage || true; echo " -> Running jest integration tests with coverage" node scripts/jest_integration --ci --verbose --coverage || true; diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 648605135b3595..0198a5d0ac5fac 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -25,14 +25,6 @@ else echo " -> running tests from the clone folder" node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" --exclude-tag "skipCoverage" || true; - if [[ -d ../target/kibana-coverage/functional ]]; then - echo " -> replacing kibana${CI_GROUP} with kibana in json files" - sed -i "s|kibana${CI_GROUP}|kibana|g" ../target/kibana-coverage/functional/*.json - echo " -> copying coverage to the original folder" - mkdir -p ../../kibana/target/kibana-coverage/functional - mv ../target/kibana-coverage/functional/* ../../kibana/target/kibana-coverage/functional/ - fi - echo " -> moving junit output, silently fail in case of no report" mkdir -p ../../kibana/target/junit mv ../target/junit/* ../../kibana/target/junit/ || echo "copying junit failed" diff --git a/x-pack/plugins/alerts/README.md b/x-pack/plugins/alerts/README.md index 2191b23eec11ee..aab848d4555d2d 100644 --- a/x-pack/plugins/alerts/README.md +++ b/x-pack/plugins/alerts/README.md @@ -20,26 +20,13 @@ Table of Contents - [Example](#example) - [Role Based Access-Control](#role-based-access-control) - [Alert Navigation](#alert-navigation) - - [RESTful API](#restful-api) - - [`POST /api/alerts/alert`: Create alert](#post-apialert-create-alert) - - [`DELETE /api/alerts/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) - - [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts) - - [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert) + - [Experimental RESTful API](#restful-api) - [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) - [`GET /api/alerts/alert/{id}/_instance_summary`: Get alert instance summary](#get-apialertidstate-get-alert-instance-summary) - - [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types) - - [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert) - - [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) - - [`POST /api/alerts/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) - - [`POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) - - [`POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) - - [`POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) - - [`POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) - [`POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) - - [Schedule Formats](#schedule-formats) - [Alert instance factory](#alert-instance-factory) - [Templating actions](#templating-actions) - - [Examples](#examples) + - [Examples](#examples) ## Terminology @@ -61,7 +48,7 @@ A Kibana alert detects a condition and executes one or more actions when that co 1. Develop and register an alert type (see alert types -> example). 2. Configure feature level privileges using RBAC -3. Create an alert using the RESTful API (see alerts -> create). +3. Create an alert using the RESTful API [Documentation](https://www.elastic.co/guide/en/kibana/master/alerts-api-update.html) (see alerts -> create). ## Limitations @@ -96,6 +83,7 @@ The following table describes the properties of the `options` object. |validate.params|When developing an alert type, you can choose to accept a series of parameters. You may also have the parameters validated before they are passed to the `executor` function or created as an alert saved object. In order to do this, provide a `@kbn/config-schema` schema that we will use to validate the `params` attribute.|@kbn/config-schema| |executor|This is where the code of the alert type lives. This is a function to be called when executing an alert on an interval basis. For full details, see executor section below.|Function| |producer|The id of the application producing this alert type.|string| +|minimumLicenseRequired|The value of a minimum license. Most of the alerts are licensed as "basic".|string| ### Executor @@ -142,13 +130,13 @@ This example receives server and threshold as parameters. It will read the CPU u ```typescript import { schema } from '@kbn/config-schema'; +import { AlertType, AlertExecutorOptions } from '../../../alerts/server'; import { - Alert, - AlertTypeParams, - AlertTypeState, - AlertInstanceState, - AlertInstanceContext -} from 'x-pack/plugins/alerts/common'; + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../../../alerts/common'; ... interface MyAlertTypeParams extends AlertTypeParams { server: string; @@ -156,7 +144,7 @@ interface MyAlertTypeParams extends AlertTypeParams { } interface MyAlertTypeState extends AlertTypeState { - lastChecked: number; + lastChecked: Date; } interface MyAlertTypeInstanceState extends AlertInstanceState { @@ -257,83 +245,6 @@ const myAlertType: AlertType< server.newPlatform.setup.plugins.alerts.registerType(myAlertType); ``` -This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server. - -```typescript -server.newPlatform.setup.plugins.alerts.registerType({ - id: 'my-alert-type', - name: 'My alert type', - validate: { - params: schema.object({ - threshold: schema.number({ min: 0, max: 1 }), - }), - }, - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - actionVariables: { - context: [ - { name: 'server', description: 'the server' }, - { name: 'hasCpuUsageIncreased', description: 'boolean indicating if the cpu usage has increased' }, - ], - state: [ - { name: 'cpuUsage', description: 'CPU usage' }, - ], - }, - async executor({ - alertId, - startedAt, - previousStartedAt, - services, - params, - state, - }: AlertExecutorOptions) { - const { threshold } = params; // Let's assume params is { threshold: 0.8 } - - // Call a function to get the CPU readings on all the servers. The result will be - // an array of { server, cpuUsage }. - const cpuUsageByServer = await getCpuUsageByServer(); - - for (const { server, cpuUsage: currentCpuUsage } of cpuUsageByServer) { - // Only execute if CPU usage is greater than threshold - if (currentCpuUsage > threshold) { - // The first argument is a unique identifier the alert instance is about. In this scenario - // the provided server will be used. Also, this id will be used to make `getState()` return - // previous state, if any, on matching identifiers. - const alertInstance = services.alertInstanceFactory(server); - - // State from last execution. This will exist if an alert instance was created and executed - // in the previous execution - const { cpuUsage: previousCpuUsage } = alertInstance.getState(); - - // Replace state entirely with new values - alertInstance.replaceState({ - cpuUsage: currentCpuUsage, - }); - - // 'default' refers to the id of a group of actions to be scheduled for execution, see 'actions' in create alert section - alertInstance.scheduleActions('default', { - server, - hasCpuUsageIncreased: currentCpuUsage > previousCpuUsage, - }); - } - } - - // Single object containing state that isn't specific to a server, this will become available - // within the `state` function parameter at the next execution - return { - lastChecked: new Date(), - }; - }, - producer: 'alerting', -}); -``` - ## Role Based Access-Control Once you have registered your AlertType, you need to grant your users privileges to use it. When registering a feature in Kibana you can specify multiple types of privileges which are granted to users when they're assigned certain roles. @@ -387,29 +298,37 @@ It's important to note that any role can be granted a mix of `all` and `read` pr ```typescript features.registerKibanaFeature({ - id: 'my-application-id', - name: 'My Application', - app: [], - privileges: { - all: { - alerting: { - all: [ - 'my-application-id.my-alert-type', - 'my-application-id.my-restricted-alert-type' - ], - }, - }, - read: { - alerting: { - all: [ - 'my-application-id.my-alert-type' - ] - read: [ - 'my-application-id.my-restricted-alert-type' - ], - }, - }, - }, + 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: [], + }, + }, }); ``` @@ -494,46 +413,10 @@ The only case in which this handler will not be used to evaluate the navigation You can use the `registerNavigation` api to specify as many AlertType specific handlers as you like, but you can only use it once per AlertType as we wouldn't know which handler to use if you specified two for the same AlertType. For the same reason, you can only use `registerDefaultNavigation` once per plugin, as it covers all cases for your specific plugin. -## RESTful API - -Using an alert type requires you to create an alert that will contain parameters and actions for a given alert type. See below for CRUD operations using the API. +## Experimental RESTful API -### `POST /api/alerts/alert`: Create alert - -Payload: - -|Property|Description|Type| -|---|---|---| -|enabled|Indicate if you want the alert to start executing on an interval basis after it has been created.|boolean| -|name|A name to reference and search in the future.|string| -|tags|A list of keywords to reference and search in the future.|string[]| -|alertTypeId|The id value of the alert type you want to call when the alert is scheduled to execute.|string| -|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| -|throttle|A Duration specifying how often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications over this period.|string| -|params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| -|actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| - -### `DELETE /api/alerts/alert/{id}`: Delete alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to delete.|string| - -### `GET /api/alerts/_find`: Find alerts - -Params: - -See the saved objects API documentation for find. All the properties are the same except you cannot pass in `type`. - -### `GET /api/alerts/alert/{id}`: Get alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to get.|string| +Using of the alert type requires you to create an alert that will contain parameters and actions for a given alert type. API description for CRUD operations is a part of the [user documentation](https://www.elastic.co/guide/en/kibana/master/alerts-api-update.html). +API listed below is experimental and could be changed or removed in the future. ### `GET /api/alerts/alert/{id}/state`: Get alert state @@ -560,93 +443,12 @@ Query: |---|---|---| |dateStart|The date to start looking for alert events in the event log. Either an ISO date string, or a duration string indicating time since now.|string| -### `GET /api/alerts/list_alert_types`: List alert types - -No parameters. - -### `PUT /api/alerts/alert/{id}`: Update alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to update.|string| - -Payload: - -|Property|Description|Type| -|---|---|---| -|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| -|throttle|A Duration specifying how often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications over this period.|string| -|name|A name to reference and search in the future.|string| -|tags|A list of keywords to reference and search in the future.|string[]| -|params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| -|actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): There map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| - -### `POST /api/alerts/alert/{id}/_enable`: Enable an alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to enable.|string| - -### `POST /api/alerts/alert/{id}/_disable`: Disable an alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to disable.|string| - -### `POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to mute all alert instances for.|string| - -### `POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance - -Params: - -|Property|Description|Type| -|---|---|---| -|alertId|The id of the alert you're trying to mute an instance for.|string| -|alertInstanceId|The instance id of the alert instance you're trying to mute.|string| - -### `POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to unmute all alert instances for.|string| - -### `POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance - -Params: - -|Property|Description|Type| -|---|---|---| -|alertId|The id of the alert you're trying to unmute an instance for.|string| -|alertInstanceId|The instance id of the alert instance you're trying to unmute.|string| - ### `POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key |Property|Description|Type| |---|---|---| |id|The id of the alert you're trying to update the API key for. System will use user in request context to generate an API key for.|string| -## Schedule Formats -A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule. - -We currently support the _Interval format_ which specifies the interval in seconds, minutes, hours or days at which the alert should execute. -Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. - -There are plans to support multiple other schedule formats in the near future. - ## Alert instance factory **alertInstanceFactory(id)** @@ -694,7 +496,7 @@ When an alert instance executes, the first argument is the `group` of actions to The templating engine is [mustache]. General definition for the [mustache variable] is a double-brace {{}}. All variables are HTML-escaped by default and if there is a requirement to render unescaped HTML, it should be applied the triple mustache: `{{{name}}}`. Also, can be used `&` to unescape a variable. -## Examples +### Examples The following code would be within an alert type. As you can see `cpuUsage ` will replace the state of the alert instance and `server` is the context for the alert instance to execute. The difference between the two is `cpuUsage ` will be accessible at the next execution. diff --git a/x-pack/plugins/apm/common/utils/queries.test.ts b/x-pack/plugins/apm/common/utils/queries.test.ts new file mode 100644 index 00000000000000..546c8627def69c --- /dev/null +++ b/x-pack/plugins/apm/common/utils/queries.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; +import { ENVIRONMENT_NOT_DEFINED } from '../environment_filter_values'; +import { environmentQuery } from './queries'; + +describe('environmentQuery', () => { + describe('when environment is undefined', () => { + it('returns an empty query', () => { + expect(environmentQuery()).toEqual([]); + }); + }); + + it('creates a query for a service environment', () => { + expect(environmentQuery('test')).toEqual([ + { + term: { [SERVICE_ENVIRONMENT]: 'test' }, + }, + ]); + }); + + it('creates a query for missing service environments', () => { + expect(environmentQuery(ENVIRONMENT_NOT_DEFINED.value)[0]).toHaveProperty( + ['bool', 'must_not', 'exists', 'field'], + SERVICE_ENVIRONMENT + ); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts b/x-pack/plugins/apm/common/utils/queries.ts similarity index 52% rename from x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts rename to x-pack/plugins/apm/common/utils/queries.ts index 3dea3344085c92..dbbbf324b964a9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts +++ b/x-pack/plugins/apm/common/utils/queries.ts @@ -5,19 +5,41 @@ * 2.0. */ -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ENVIRONMENT_NOT_DEFINED, ENVIRONMENT_ALL, -} from '../../../../common/environment_filter_values'; -import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; +} from '../environment_filter_values'; +import { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; -export function getEnvironmentUiFilterES(environment?: string): ESFilter[] { +type QueryContainer = ESFilter; + +export function environmentQuery(environment?: string): QueryContainer[] { if (!environment || environment === ENVIRONMENT_ALL.value) { return []; } + if (environment === ENVIRONMENT_NOT_DEFINED.value) { return [{ bool: { must_not: { exists: { field: SERVICE_ENVIRONMENT } } } }]; } + return [{ term: { [SERVICE_ENVIRONMENT]: environment } }]; } + +export function rangeQuery( + start: number, + end: number, + field = '@timestamp' +): QueryContainer[] { + return [ + { + range: { + [field]: { + gte: start, + lte: end, + format: 'epoch_millis', + }, + }, + }, + ]; +} diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index 3edf21eae7279f..4cd2db43621a8f 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -68,7 +68,7 @@ type ErrorGroupDetailsProps = RouteComponentProps<{ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { const { serviceName, groupId } = match.params; const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data: errorGroupData } = useFetcher( (callApmApi) => { @@ -81,6 +81,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { groupId, }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -89,7 +90,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { }); } }, - [serviceName, start, end, groupId, uiFilters] + [environment, serviceName, start, end, groupId, uiFilters] ); const { errorDistributionData } = useErrorGroupDistributionFetcher({ diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx new file mode 100644 index 00000000000000..3a9100a0712aab --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx @@ -0,0 +1,139 @@ +/* + * 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 * as React from 'react'; +import numeral from '@elastic/numeral'; +import styled from 'styled-components'; +import { useContext, useEffect } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiToolTip, + EuiIconTip, +} from '@elastic/eui'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { I18LABELS } from '../translations'; +import { useUxQuery } from '../hooks/useUxQuery'; +import { formatToSec } from '../UXMetrics/KeyUXMetrics'; +import { CsmSharedContext } from '../CsmSharedContext'; + +const ClFlexGroup = styled(EuiFlexGroup)` + flex-direction: row; + @media only screen and (max-width: 768px) { + flex-direction: row; + justify-content: space-between; + } +`; + +function formatTitle(unit: string, value?: number) { + if (typeof value === 'undefined') return I18LABELS.dataMissing; + return formatToSec(value, unit); +} + +function PageViewsTotalTitle({ pageViews }: { pageViews?: number }) { + if (typeof pageViews === 'undefined') { + return <>{I18LABELS.dataMissing}; + } + return pageViews < 10000 ? ( + <>{numeral(pageViews).format('0,0')} + ) : ( + + <>{numeral(pageViews).format('0 a')} + + ); +} + +export function Metrics() { + const uxQuery = useUxQuery(); + + const { data, status } = useFetcher( + (callApmApi) => { + if (uxQuery) { + return callApmApi({ + endpoint: 'GET /api/apm/rum/client-metrics', + params: { + query: { + ...uxQuery, + }, + }, + }); + } + return Promise.resolve(null); + }, + [uxQuery] + ); + + const { setSharedData } = useContext(CsmSharedContext); + + useEffect(() => { + setSharedData({ totalPageViews: data?.pageViews?.value ?? 0 }); + }, [data, setSharedData]); + + const STAT_STYLE = { minWidth: '150px', maxWidth: '250px' }; + + return ( + + + + {I18LABELS.totalPageLoad} + + + } + isLoading={status !== 'success'} + /> + + + + {I18LABELS.backEnd} + + + } + isLoading={status !== 'success'} + /> + + + + {I18LABELS.frontEnd} + + + } + isLoading={status !== 'success'} + /> + + + } + description={I18LABELS.pageViews} + isLoading={status !== 'success'} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 1d6ce21d198c4a..add6ac1b08b281 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -6,134 +6,36 @@ */ import * as React from 'react'; -import numeral from '@elastic/numeral'; -import styled from 'styled-components'; -import { useContext, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiStat, - EuiToolTip, - EuiIconTip, + EuiPanel, + EuiTitle, + EuiSpacer, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; -import { useUxQuery } from '../hooks/useUxQuery'; -import { formatToSec } from '../UXMetrics/KeyUXMetrics'; -import { CsmSharedContext } from '../CsmSharedContext'; - -const ClFlexGroup = styled(EuiFlexGroup)` - flex-direction: row; - @media only screen and (max-width: 768px) { - flex-direction: row; - justify-content: space-between; - } -`; - -function formatTitle(unit: string, value?: number) { - if (typeof value === 'undefined') return I18LABELS.dataMissing; - return formatToSec(value, unit); -} - -function PageViewsTotalTitle({ pageViews }: { pageViews?: number }) { - if (typeof pageViews === 'undefined') { - return <>{I18LABELS.dataMissing}; - } - return pageViews < 10000 ? ( - <>{numeral(pageViews).format('0,0')} - ) : ( - - <>{numeral(pageViews).format('0 a')} - - ); -} +import { getPercentileLabel } from '../UXMetrics/translations'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { Metrics } from './Metrics'; export function ClientMetrics() { - const uxQuery = useUxQuery(); - - const { data, status } = useFetcher( - (callApmApi) => { - if (uxQuery) { - return callApmApi({ - endpoint: 'GET /api/apm/rum/client-metrics', - params: { - query: { - ...uxQuery, - }, - }, - }); - } - return Promise.resolve(null); - }, - [uxQuery] - ); - - const { setSharedData } = useContext(CsmSharedContext); - - useEffect(() => { - setSharedData({ totalPageViews: data?.pageViews?.value ?? 0 }); - }, [data, setSharedData]); - - const STAT_STYLE = { width: '240px' }; + const { + urlParams: { percentile }, + } = useUrlParams(); return ( - - - - {I18LABELS.totalPageLoad} - - - } - isLoading={status !== 'success'} - /> - - - - {I18LABELS.backEnd} - - - } - isLoading={status !== 'success'} - /> - - - - {I18LABELS.frontEnd} - - - } - isLoading={status !== 'success'} - /> - - - } - description={I18LABELS.pageViews} - isLoading={status !== 'success'} - /> - - + + + + +

+ {I18LABELS.pageLoad} ({getPercentileLabel(percentile!)}) +

+
+ + +
+
+
); } diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx index 4afecb7623f73e..5b0b475e86e038 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx @@ -11,12 +11,14 @@ import { EuiSpacer, EuiHorizontalRule, EuiButtonEmpty, + EuiAccordion, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { Filter } from './Filter'; import { useLocalUIFilters } from '../hooks/useLocalUIFilters'; import { LocalUIFilterName } from '../../../../../common/ui_filter'; +import { useBreakPoints } from '../../../../hooks/use_break_points'; interface Props { filterNames: LocalUIFilterName[]; @@ -45,16 +47,20 @@ function LocalUIFilters({ const hasValues = filters.some((filter) => filter.value.length > 0); - return ( + const { isSmall } = useBreakPoints(); + + const title = ( + +

+ {i18n.translate('xpack.apm.localFiltersTitle', { + defaultMessage: 'Filters', + })} +

+
+ ); + + const content = ( <> - -

- {i18n.translate('xpack.apm.localFiltersTitle', { - defaultMessage: 'Filters', - })} -

-
- {children} {filters.map((filter) => { return ( @@ -90,6 +96,18 @@ function LocalUIFilters({ ) : null} ); + + return isSmall ? ( + + {content} + + ) : ( + <> + {title} + + {content} + + ); } export { LocalUIFilters }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 7d34328d2653dd..e3e2a979c48d35 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -13,6 +13,7 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { UserPercentile } from '../UserPercentile'; +import { useBreakPoints } from '../../../../hooks/use_break_points'; export function MainFilters() { const { @@ -37,6 +38,11 @@ export function MainFilters() { [start, end] ); + const { isSmall } = useBreakPoints(); + + // on mobile we want it to take full width + const envStyle = isSmall ? {} : { maxWidth: 200 }; + return ( <> @@ -45,7 +51,7 @@ export function MainFilters() { serviceNames={data ?? []} /> - + diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index 50d91ca73c249d..a182de8540f584 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -5,46 +5,22 @@ * 2.0. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiSpacer, - EuiPanel, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { ClientMetrics } from './ClientMetrics'; -import { I18LABELS } from './translations'; import { UXMetrics } from './UXMetrics'; import { ImpactfulMetrics } from './ImpactfulMetrics'; import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; import { useBreakPoints } from '../../../hooks/use_break_points'; -import { getPercentileLabel } from './UXMetrics/translations'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ClientMetrics } from './ClientMetrics'; export function RumDashboard() { - const { - urlParams: { percentile }, - } = useUrlParams(); const { isSmall } = useBreakPoints(); return ( - - - - -

- {I18LABELS.pageLoad} ({getPercentileLabel(percentile!)}) -

-
- - -
-
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx index 6e3ed54ed2e780..a0b3781a30b209 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx @@ -20,24 +20,15 @@ export const UX_LABEL = i18n.translate('xpack.apm.ux.title', { export function RumHome() { return ( - +

{UX_LABEL}

+ - - - - - - +
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx index d3a34a1df25f7a..965449b78f3e08 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -206,6 +206,7 @@ export function SelectableUrlList({ panelRef={setPopoverRef} button={search} closePopover={closePopover} + style={{ minWidth: 200 }} >
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index f2e761128e5cea..b8766e8b5ce67d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -36,7 +36,7 @@ export function formatToSec( } return (valueInMs / 1000).toFixed(2) + ' s'; } -const STAT_STYLE = { width: '240px' }; +const STAT_STYLE = { width: '200px' }; interface Props { data?: UXMetrics | null; @@ -71,7 +71,7 @@ export function KeyUXMetrics({ data, loading }: Props) { // Note: FCP value is in ms unit return ( - + - +

diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index 7386209310c1fb..9b80ee6fc31b85 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -51,7 +51,13 @@ export function ErrorCorrelations({ onClose }: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionName, transactionType, start, end } = urlParams; + const { + environment, + transactionName, + transactionType, + start, + end, + } = urlParams; const { defaultFieldNames } = useFieldNames(); const [fieldNames, setFieldNames] = useLocalStorage( `apm.correlations.errors.fields:${serviceName}`, @@ -65,6 +71,7 @@ export function ErrorCorrelations({ onClose }: Props) { endpoint: 'GET /api/apm/correlations/failed_transactions', params: { query: { + environment, serviceName, transactionName, transactionType, @@ -78,6 +85,7 @@ export function ErrorCorrelations({ onClose }: Props) { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index c88aaa85bb856f..459df99a62f5a2 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -49,7 +49,13 @@ export function LatencyCorrelations({ onClose }: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionName, transactionType, start, end } = urlParams; + const { + environment, + transactionName, + transactionType, + start, + end, + } = urlParams; const { defaultFieldNames } = useFieldNames(); const [fieldNames, setFieldNames] = useLocalStorage( `apm.correlations.latency.fields:${serviceName}`, @@ -70,6 +76,7 @@ export function LatencyCorrelations({ onClose }: Props) { endpoint: 'GET /api/apm/correlations/slow_transactions', params: { query: { + environment, serviceName, transactionName, transactionType, @@ -84,6 +91,7 @@ export function LatencyCorrelations({ onClose }: Props) { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 29bdf6467e5447..bde23eddaa44fa 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -28,7 +28,7 @@ interface ErrorGroupOverviewProps { export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, sortField, sortDirection } = urlParams; + const { environment, start, end, sortField, sortDirection } = urlParams; const { errorDistributionData } = useErrorGroupDistributionFetcher({ serviceName, groupId: undefined, @@ -46,6 +46,7 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { serviceName, }, query: { + environment, start, end, sortField, @@ -56,7 +57,7 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { }); } }, - [serviceName, start, end, sortField, sortDirection, uiFilters] + [environment, serviceName, start, end, sortField, sortDirection, uiFilters] ); useTrackPageview({ diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 1cb420a8ac1949..cd17ca0ce023d2 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -39,19 +39,24 @@ function useServicesFetcher() { const { urlParams, uiFilters } = useUrlParams(); const { core } = useApmPluginContext(); const upgradeAssistantHref = useUpgradeAssistantHref(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data = initialData, status } = useFetcher( (callApmApi) => { if (start && end) { return callApmApi({ endpoint: 'GET /api/apm/services', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + environment, + start, + end, + uiFilters: JSON.stringify(uiFilters), + }, }, }); } }, - [start, end, uiFilters] + [environment, start, end, uiFilters] ); useEffect(() => { @@ -118,7 +123,7 @@ export function ServiceInventory() { return ( <> - + {displayMlCallout ? ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts b/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts new file mode 100644 index 00000000000000..fda45db98d0cae --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts @@ -0,0 +1,38 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; + +export function getLatencyColumnLabel( + latencyAggregationType?: LatencyAggregationType +) { + switch (latencyAggregationType) { + case LatencyAggregationType.avg: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnAvgLabel', { + defaultMessage: 'Latency (avg.)', + }); + + case LatencyAggregationType.p95: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnP95Label', { + defaultMessage: 'Latency (95th)', + }); + + case LatencyAggregationType.p99: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnP99Label', { + defaultMessage: 'Latency (99th)', + }); + + default: + return i18n.translate( + 'xpack.apm.serviceOverview.latencyColumnDefaultLabel', + { + defaultMessage: 'Latency', + } + ); + } +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index db0b8283a28c8c..a0ea00ae5c3ad8 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -57,11 +57,7 @@ export function ServiceOverview({ return ( - + diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index baae683e2eba95..a4647bc148b1e2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -14,10 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { - ENVIRONMENT_ALL, - getNextEnvironmentUrlParam, -} from '../../../../../common/environment_filter_values'; +import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; import { asMillisecondDuration, asPercent, @@ -96,7 +93,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { name: i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableColumnLatency', { - defaultMessage: 'Latency', + defaultMessage: 'Latency (avg.)', } ), width: px(unit * 10), @@ -182,7 +179,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { query: { start, end, - environment: environment || ENVIRONMENT_ALL.value, + environment, numBuckets: 20, }, }, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index 7f4823c13d593a..f7f5db32e986cc 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -52,7 +52,7 @@ const DEFAULT_SORT = { export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { - urlParams: { start, end }, + urlParams: { environment, start, end }, uiFilters, } = useUrlParams(); const { transactionType } = useApmServiceContext(); @@ -152,6 +152,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -178,6 +179,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { }); }, [ + environment, start, end, serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index e7c1d4442e3b7c..2f2aaf3156b93a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -25,13 +25,13 @@ export function ServiceOverviewInstancesChartAndTable({ const { transactionType } = useApmServiceContext(); const { - urlParams: { start, end }, + urlParams: { environment, latencyAggregationType, start, end }, uiFilters, } = useUrlParams(); const { data = [], status } = useFetcher( (callApmApi) => { - if (!start || !end || !transactionType) { + if (!start || !end || !transactionType || !latencyAggregationType) { return; } @@ -43,6 +43,8 @@ export function ServiceOverviewInstancesChartAndTable({ serviceName, }, query: { + environment, + latencyAggregationType, start, end, transactionType, @@ -52,7 +54,15 @@ export function ServiceOverviewInstancesChartAndTable({ }, }); }, - [start, end, serviceName, transactionType, uiFilters] + [ + environment, + latencyAggregationType, + start, + end, + serviceName, + transactionType, + uiFilters, + ] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index 62ae4b7bc34462..83ad506e8659b6 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -24,6 +24,7 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; @@ -32,6 +33,7 @@ import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { getLatencyColumnLabel } from '../get_latency_column_label'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; type ServiceInstanceItem = ValuesType< @@ -50,6 +52,9 @@ export function ServiceOverviewInstancesTable({ status, }: Props) { const { agentName } = useApmServiceContext(); + const { + urlParams: { latencyAggregationType }, + } = useUrlParams(); const columns: Array> = [ { @@ -95,12 +100,7 @@ export function ServiceOverviewInstancesTable({ }, { field: 'latencyValue', - name: i18n.translate( - 'xpack.apm.serviceOverview.instancesTableColumnLatency', - { - defaultMessage: 'Latency', - } - ), + name: getLatencyColumnLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency }) => { return ( @@ -182,7 +182,7 @@ export function ServiceOverviewInstancesTable({ defaultMessage: 'Memory usage (avg.)', } ), - width: px(unit * 8), + width: px(unit * 9), render: (_, { memoryUsage }) => { return ( (); const { urlParams, uiFilters } = useUrlParams(); const { transactionType } = useApmServiceContext(); - const { start, end, comparisonEnabled, comparisonType } = urlParams; - const comparisonChartTheme = getComparisonChartTheme(theme); - const { - comparisonStart = undefined, - comparisonEnd = undefined, - } = comparisonType - ? getTimeRangeComparison({ - start, - end, - comparisonType, - }) - : {}; + const { environment, start, end } = urlParams; const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { @@ -57,26 +42,17 @@ export function ServiceOverviewThroughputChart({ serviceName, }, query: { + environment, start, end, transactionType, uiFilters: JSON.stringify(uiFilters), - comparisonStart, - comparisonEnd, }, }, }); } }, - [ - serviceName, - start, - end, - uiFilters, - transactionType, - comparisonStart, - comparisonEnd, - ] + [environment, serviceName, start, end, uiFilters, transactionType] ); return ( @@ -93,7 +69,6 @@ export function ServiceOverviewThroughputChart({ height={height} showAnnotations={false} fetchStatus={status} - customTheme={comparisonChartTheme} timeseries={[ { data: data.currentPeriod, @@ -104,21 +79,6 @@ export function ServiceOverviewThroughputChart({ { defaultMessage: 'Throughput' } ), }, - ...(comparisonEnabled - ? [ - { - data: data.previousPeriod, - type: 'area', - color: theme.eui.euiColorLightestShade, - title: i18n.translate( - 'xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel', - { - defaultMessage: 'Previous period', - } - ), - }, - ] - : []), ]} yLabelFormat={asTransactionRate} /> diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 2ffc0fc9c93a3a..b01ad93f2c9988 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -9,6 +9,7 @@ import { EuiBasicTableColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ValuesType } from 'utility-types'; +import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { asMillisecondDuration, asPercent, @@ -20,6 +21,7 @@ import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { getLatencyColumnLabel } from '../get_latency_column_label'; type TransactionGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; @@ -28,35 +30,13 @@ type ServiceTransactionGroupItem = ValuesType< >; type TransactionGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; -function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { - switch (latencyAggregationType) { - case 'avg': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg', - { defaultMessage: 'Latency (avg.)' } - ); - - case 'p95': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95', - { defaultMessage: 'Latency (95th)' } - ); - - case 'p99': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99', - { defaultMessage: 'Latency (99th)' } - ); - } -} - export function getColumns({ serviceName, latencyAggregationType, transactionGroupComparisonStatistics, }: { serviceName: string; - latencyAggregationType?: string; + latencyAggregationType?: LatencyAggregationType; transactionGroupComparisonStatistics?: TransactionGroupComparisonStatistics; }): Array> { return [ @@ -88,7 +68,7 @@ export function getColumns({ { field: 'latency', sortable: true, - name: getLatencyAggregationTypeLabel(latencyAggregationType), + name: getLatencyColumnLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency, name }) => { const timeseries = diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index a0facb2ddbedf6..5529f9028b9dda 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -58,7 +58,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const { transactionType } = useApmServiceContext(); const { uiFilters, - urlParams: { start, end, latencyAggregationType }, + urlParams: { environment, start, end, latencyAggregationType }, } = useUrlParams(); const { data = INITIAL_STATE, status } = useFetcher( @@ -72,6 +72,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -87,6 +88,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { }); }, [ + environment, serviceName, start, end, @@ -125,6 +127,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index cae0ef2de2ad1b..8fc9ac12824baa 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -23,7 +23,7 @@ const DEFAULT_RESPONSE: TracesAPIResponse = { export function TraceOverview() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { status, data = DEFAULT_RESPONSE } = useFetcher( (callApmApi) => { if (start && end) { @@ -31,6 +31,7 @@ export function TraceOverview() { endpoint: 'GET /api/apm/traces', params: { query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -39,7 +40,7 @@ export function TraceOverview() { }); } }, - [start, end, uiFilters] + [environment, start, end, uiFilters] ); useTrackPageview({ app: 'apm', path: 'traces_overview' }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index a6fe2b170bc3b8..0a322cfc9c80b0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -95,7 +95,7 @@ export function TransactionDetails({

{transactionName}

- + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 720a3857ef5209..97be35ec6f5b95 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -82,7 +82,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { return ( <> - + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index 8fb5166bd8676e..e4fbd075660605 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -131,7 +131,7 @@ describe('TransactionOverview', () => { }); expect(history.location.search).toEqual( - '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=day' + '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now' ); expect(getByText(container, 'firstType')).toBeInTheDocument(); expect(getByText(container, 'secondType')).toBeInTheDocument(); @@ -142,7 +142,7 @@ describe('TransactionOverview', () => { expect(history.push).toHaveBeenCalled(); expect(history.location.search).toEqual( - '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=day' + '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now' ); }); }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 406ba98b79e256..a63788457b8b5d 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -21,7 +21,7 @@ const DEFAULT_RESPONSE: Partial = { export function useTransactionListFetcher() { const { urlParams, uiFilters } = useUrlParams(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { transactionType, start, end } = urlParams; + const { environment, transactionType, start, end } = urlParams; const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { @@ -30,6 +30,7 @@ export function useTransactionListFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -39,7 +40,7 @@ export function useTransactionListFetcher() { }); } }, - [serviceName, start, end, transactionType, uiFilters] + [environment, serviceName, start, end, transactionType, uiFilters] ); return { diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index 714645fd749612..59c99463144cbe 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -64,10 +64,9 @@ export function EnvironmentFilter() { const history = useHistory(); const location = useLocation(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { uiFilters, urlParams } = useUrlParams(); + const { urlParams } = useUrlParams(); - const { environment } = uiFilters; - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { environments, status = 'loading' } = useEnvironmentsFetcher({ serviceName, start, diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index 3bfdb5df61c2ee..c3d418b63426b5 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import { EuiLink } from '@elastic/eui'; import React from 'react'; import { APMQueryParams } from '../url_helpers'; -import { APMLinkExtendProps, useAPMHref } from './APMLink'; +import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; const persistedFilters: Array = [ 'host', @@ -29,6 +28,5 @@ interface Props extends APMLinkExtendProps { } export function MetricOverviewLink({ serviceName, ...rest }: Props) { - const href = useMetricOverviewHref(serviceName); - return ; + return ; } diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index 7bfe17e82bf4ae..8009f288d48c0b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -24,7 +24,6 @@ import { } from '@elastic/charts'; import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useChartTheme } from '../../../../../observability/public'; @@ -32,7 +31,6 @@ import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { unit } from '../../../style/variables'; @@ -78,14 +76,13 @@ export function TimeseriesChart({ const history = useHistory(); const { annotations } = useAnnotationsContext(); const { setPointerEvent, chartRef } = useChartPointerEventContext(); - const { urlParams } = useUrlParams(); const theme = useTheme(); const chartTheme = useChartTheme(); - const { start, end } = urlParams; + const xValues = timeseries.flatMap(({ data }) => data.map(({ x }) => x)); - const min = moment.utc(start).valueOf(); - const max = moment.utc(end).valueOf(); + const min = Math.min(...xValues); + const max = Math.max(...xValues); const xFormatter = niceTimeFormatter([min, max]); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index 466f201ab3398e..293a1929ca6069 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -13,7 +13,7 @@ import { useApmServiceContext } from '../../../../context/apm_service/use_apm_se export function useTransactionBreakdown() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { start, end, transactionName } = urlParams; + const { environment, start, end, transactionName } = urlParams; const { transactionType } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( @@ -25,6 +25,7 @@ export function useTransactionBreakdown() { params: { path: { serviceName }, query: { + environment, start, end, transactionName, @@ -35,7 +36,15 @@ export function useTransactionBreakdown() { }); } }, - [serviceName, start, end, transactionType, transactionName, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionType, + transactionName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index df18e7627faedb..a3da8812966f18 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -33,7 +33,7 @@ export function TransactionErrorRateChart({ const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { transactionType } = useApmServiceContext(); - const { start, end, transactionName } = urlParams; + const { environment, start, end, transactionName } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -46,6 +46,7 @@ export function TransactionErrorRateChart({ serviceName, }, query: { + environment, start, end, transactionType, @@ -56,7 +57,15 @@ export function TransactionErrorRateChart({ }); } }, - [serviceName, start, end, uiFilters, transactionType, transactionName] + [ + environment, + serviceName, + start, + end, + uiFilters, + transactionType, + transactionName, + ] ); const errorRates = data?.transactionErrorRate || []; diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts index bdce54ad086342..4de68a5bc20362 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts @@ -13,7 +13,7 @@ describe('url_params_context helpers', () => { describe('getDateRange', () => { describe('with non-rounded dates', () => { describe('one minute', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -21,13 +21,13 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:48:55.304Z', }) ).toEqual({ - start: '2021-01-28T05:47:50.000Z', - end: '2021-01-28T05:49:00.000Z', + start: '2021-01-28T05:47:00.000Z', + end: '2021-01-28T05:48:55.304Z', }); }); }); describe('one day', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -35,14 +35,14 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:46:13.367Z', }) ).toEqual({ - start: '2021-01-27T03:00:00.000Z', - end: '2021-01-28T06:00:00.000Z', + start: '2021-01-27T05:46:00.000Z', + end: '2021-01-28T05:46:13.367Z', }); }); }); describe('one year', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -50,8 +50,8 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:52:39.741Z', }) ).toEqual({ - start: '2020-01-01T00:00:00.000Z', - end: '2021-02-01T00:00:00.000Z', + start: '2020-01-28T05:52:00.000Z', + end: '2021-01-28T05:52:39.741Z', }); }); }); diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts index f77c4119f46e1a..eae9eba8b3ddad 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts @@ -6,8 +6,8 @@ */ import datemath from '@elastic/datemath'; -import { scaleUtc } from 'd3-scale'; import { compact, pickBy } from 'lodash'; +import moment from 'moment'; import { IUrlParams } from './types'; function getParsedDate(rawDate?: string, options = {}) { @@ -42,13 +42,12 @@ export function getDateRange({ return { start: state.start, end: state.end }; } - // Calculate ticks for the time ranges to produce nicely rounded values. - const ticks = scaleUtc().domain([start, end]).nice().ticks(); + // rounds down start to minute + const roundedStart = moment(start).startOf('minute'); - // Return the first and last tick values. return { - start: ticks[0].toISOString(), - end: ticks[ticks.length - 1].toISOString(), + start: roundedStart.toISOString(), + end: end.toISOString(), }; } diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 63d719205c2ade..b6e7330be30cbd 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts @@ -6,6 +6,7 @@ */ import { Location } from 'history'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; import { pickKeys } from '../../../common/utils/pick_keys'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -66,6 +67,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { refreshInterval: refreshInterval ? toNumber(refreshInterval) : undefined, // query params + environment: toString(environment) || ENVIRONMENT_ALL.value, sortDirection, sortField, page: toNumber(page) || 0, @@ -87,7 +89,6 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { : undefined, comparisonType: comparisonType as TimeRangeComparisonType | undefined, // ui filters - environment, ...localUIFilters, }); } diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 66e22f32b26bd5..056aabb10f8784 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx @@ -52,8 +52,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2010-03-15T00:00:00.000Z', - '2010-04-11T00:00:00.000Z', + '2010-03-15T12:00:00.000Z', + '2010-04-10T12:00:00.000Z', ]); }); @@ -71,8 +71,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2009-03-15T00:00:00.000Z', - '2009-04-11T00:00:00.000Z', + '2009-03-15T12:00:00.000Z', + '2009-04-10T12:00:00.000Z', ]); }); @@ -92,7 +92,7 @@ describe('UrlParamsContext', () => { expect([params.start, params.end]).toEqual([ '1969-12-31T00:00:00.000Z', - '1970-01-01T00:00:00.000Z', + '1969-12-31T23:59:59.999Z', ]); nowSpy.mockRestore(); @@ -145,8 +145,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2005-09-19T00:00:00.000Z', - '2005-10-23T00:00:00.000Z', + '2005-09-20T12:00:00.000Z', + '2005-10-21T12:00:00.000Z', ]); }); @@ -196,7 +196,7 @@ describe('UrlParamsContext', () => { expect([params.start, params.end]).toEqual([ '2000-06-14T00:00:00.000Z', - '2000-06-15T00:00:00.000Z', + '2000-06-14T23:59:59.999Z', ]); }); }); diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 8312fedc7eb039..90245b9843b013 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -14,7 +14,6 @@ import React, { useState, } from 'react'; import { withRouter } from 'react-router-dom'; -import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { LocalUIFilterName } from '../../../common/ui_filter'; import { pickKeys } from '../../../common/utils/pick_keys'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -39,7 +38,6 @@ function useUiFilters(params: IUrlParams): UIFilters { return useDeepObjectIdentity({ kuery, - environment: environment || ENVIRONMENT_ALL.value, ...localUiFilters, }); } diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx index 834b0cc0527897..9ff179e6af6a06 100644 --- a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -16,7 +16,7 @@ export function useErrorGroupDistributionFetcher({ groupId: string | undefined; }) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data } = useFetcher( (callApmApi) => { if (start && end) { @@ -25,6 +25,7 @@ export function useErrorGroupDistributionFetcher({ params: { path: { serviceName }, query: { + environment, start, end, groupId, @@ -34,7 +35,7 @@ export function useErrorGroupDistributionFetcher({ }); } }, - [serviceName, start, end, groupId, uiFilters] + [environment, serviceName, start, end, groupId, uiFilters] ); return { errorDistributionData: data }; diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index ecfa5471189d20..87e10f1e8937b0 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -24,7 +24,7 @@ export function useServiceMetricChartsFetcher({ const { urlParams, uiFilters } = useUrlParams(); const { agentName } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && agentName) { @@ -33,6 +33,7 @@ export function useServiceMetricChartsFetcher({ params: { path: { serviceName }, query: { + environment, serviceNodeName, start, end, @@ -43,7 +44,15 @@ export function useServiceMetricChartsFetcher({ }); } }, - [serviceName, start, end, agentName, serviceNodeName, uiFilters] + [ + environment, + serviceName, + start, + end, + agentName, + serviceNodeName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 66446bf0dfebad..c493a30716aa5e 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -25,6 +25,7 @@ export function useTransactionDistributionFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { + environment, start, end, transactionType, @@ -45,6 +46,7 @@ export function useTransactionDistributionFetcher() { serviceName, }, query: { + environment, start, end, transactionType, @@ -92,7 +94,15 @@ export function useTransactionDistributionFetcher() { }, // the histogram should not be refetched if the transactionId or traceId changes // eslint-disable-next-line react-hooks/exhaustive-deps - [serviceName, start, end, transactionType, transactionName, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionType, + transactionName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index d5974ee3543a7e..cca2e99d84dfdf 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -18,7 +18,13 @@ export function useTransactionLatencyChartsFetcher() { const { transactionType } = useApmServiceContext(); const theme = useTheme(); const { - urlParams: { start, end, transactionName, latencyAggregationType }, + urlParams: { + environment, + start, + end, + transactionName, + latencyAggregationType, + }, uiFilters, } = useUrlParams(); @@ -37,6 +43,7 @@ export function useTransactionLatencyChartsFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -49,6 +56,7 @@ export function useTransactionLatencyChartsFetcher() { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts index af9a5fee24877a..55765cd40c04eb 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts @@ -18,7 +18,7 @@ export function useTransactionThroughputChartsFetcher() { const { transactionType } = useApmServiceContext(); const theme = useTheme(); const { - urlParams: { start, end, transactionName }, + urlParams: { environment, start, end, transactionName }, uiFilters, } = useUrlParams(); @@ -31,6 +31,7 @@ export function useTransactionThroughputChartsFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -41,7 +42,15 @@ export function useTransactionThroughputChartsFetcher() { }); } }, - [serviceName, start, end, transactionName, transactionType, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionName, + transactionType, + uiFilters, + ] ); const memoizedData = useMemo( diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index aac6196c4253c5..f7f6f7486091b7 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -182,8 +182,8 @@ export async function inspectSearchParams( }, } ) as APMConfig, - uiFilters: { environment: 'test' }, - esFilter: [{ term: { 'service.environment': 'test' } }], + uiFilters: {}, + esFilter: [], indices: { /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': 'myIndex', diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index e487684909633e..3457207eeee3c4 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -13,10 +13,9 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { AlertParams } from '../../../routes/alerts/chart_preview'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -39,13 +38,13 @@ export function getTransactionDurationChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), ...(transactionType ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts index 05ad743af0997b..aa85c44284d9d4 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -7,10 +7,9 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -28,9 +27,9 @@ export function getTransactionErrorCountChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts index fa01773c780703..88e249a71a81f0 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts @@ -11,9 +11,8 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { @@ -34,13 +33,13 @@ export async function getTransactionErrorRateChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), ...(transactionType ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index e9e2e078ec344e..c7861eaa819aed 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -27,7 +27,7 @@ import { SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -104,7 +104,7 @@ export function registerErrorCountAlertType({ ...(alertParams.serviceName ? [{ term: { [SERVICE_NAME]: alertParams.serviceName } }] : []), - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 480efd8d4c7ad0..704aee932a604f 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -20,7 +20,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { getDurationFormatter } from '../../../common/utils/formatters'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -96,7 +96,7 @@ export function registerTransactionDurationAlertType({ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, { term: { [SERVICE_NAME]: alertParams.serviceName } }, { term: { [TRANSACTION_TYPE]: alertParams.transactionType } }, - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 882bde87927615..6f58b7714d8324 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -22,7 +22,7 @@ import { import { EventOutcome } from '../../../common/event_outcome'; import { ProcessorEvent } from '../../../common/processor_event'; import { asDecimalOrInteger } from '../../../common/utils/formatters'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -103,7 +103,7 @@ export function registerTransactionErrorRateAlertType({ }, ] : []), - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index a560f011b186bb..d70e19bf4a5f52 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -9,15 +9,15 @@ import { Logger } from 'kibana/server'; import uuid from 'uuid/v4'; import { snakeCase } from 'lodash'; import Boom from '@hapi/boom'; -import { ProcessorEvent } from '../../../common/processor_event'; import { ML_ERRORS } from '../../../common/anomaly_detection'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/queries'; import { Setup } from '../helpers/setup_request'; import { TRANSACTION_DURATION, PROCESSOR_EVENT, } from '../../../common/elasticsearch_fieldnames'; import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { withApmSpan } from '../../utils/with_apm_span'; export async function createAnomalyDetectionJobs( @@ -86,7 +86,7 @@ async function createAnomalyDetectionJob({ filter: [ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, { exists: { field: TRANSACTION_DURATION } }, - ...getEnvironmentUiFilterES(environment), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts index 721e35e2ef60d0..ecefdfc2b3d9b2 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts @@ -13,7 +13,7 @@ import { } from '../process_significant_term_aggs'; import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch/aggregations'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -31,12 +31,14 @@ import { import { withApmSpan } from '../../../utils/with_apm_span'; export async function getCorrelationsForFailedTransactions({ + environment, serviceName, transactionType, transactionName, fieldNames, setup, }: { + environment?: string; serviceName: string | undefined; transactionType: string | undefined; transactionName: string | undefined; @@ -47,9 +49,10 @@ export async function getCorrelationsForFailedTransactions({ const { start, end, esFilter, apmEventClient } = setup; const backgroundFilters: ESFilter[] = [ - ...esFilter, - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (serviceName) { diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts index 816061da5cfc17..832b89a18d1028 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts @@ -7,7 +7,7 @@ import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch/aggregations'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NAME, TRANSACTION_DURATION, @@ -23,6 +23,7 @@ import { getLatencyDistribution } from './get_latency_distribution'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getCorrelationsForSlowTransactions({ + environment, serviceName, transactionType, transactionName, @@ -30,6 +31,7 @@ export async function getCorrelationsForSlowTransactions({ fieldNames, setup, }: { + environment?: string; serviceName: string | undefined; transactionType: string | undefined; transactionName: string | undefined; @@ -41,9 +43,10 @@ export async function getCorrelationsForSlowTransactions({ const { start, end, esFilter, apmEventClient } = setup; const backgroundFilters: ESFilter[] = [ - ...esFilter, - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (serviceName) { diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 8fedcf6224e3c9..1bf01c24776fb0 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -31,8 +31,8 @@ export async function getAllEnvironments({ includeMissing?: boolean; }) { const spanName = serviceName - ? 'get_all_environments_for_all_services' - : 'get_all_environments_for_service'; + ? 'get_all_environments_for_service' + : 'get_all_environments_for_all_services'; return withApmSpan(spanName, async () => { const { apmEventClient, config } = setup; const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; diff --git a/x-pack/plugins/apm/server/lib/environments/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts index 56f0a03910c1a4..af88493c148ce1 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_environments.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { ESFilter } from '../../../../../typings/elasticsearch'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -37,7 +36,7 @@ export async function getEnvironments({ return withApmSpan(spanName, async () => { const { start, end, apmEventClient, config } = setup; - const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; + const filter = rangeQuery(start, end); if (serviceName) { filter.push({ diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts index db8414864f5771..1712699162b73c 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts @@ -25,6 +25,7 @@ describe('get buckets', () => { }); await getBuckets({ + environment: 'prod', serviceName: 'myServiceName', bucketSize: 10, setup: { @@ -42,14 +43,8 @@ describe('get buckets', () => { get: () => 'myIndex', } ) as APMConfig, - uiFilters: { - environment: 'prod', - }, - esFilter: [ - { - term: { 'service.environment': 'prod' }, - }, - ], + uiFilters: {}, + esFilter: [], indices: { /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': 'apm-*', diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 383fcbb2f5ce72..fbe406d8d1a9d2 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -11,16 +11,18 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getBuckets({ + environment, serviceName, groupId, bucketSize, setup, }: { + environment?: string; serviceName: string; groupId?: string; bucketSize: number; @@ -30,7 +32,8 @@ export async function getBuckets({ const { start, end, esFilter, apmEventClient } = setup; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts index be3a29780a5b65..1fb0cbad4a5f09 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts @@ -14,16 +14,19 @@ function getBucketSize({ start, end }: SetupTimeRange) { } export async function getErrorDistribution({ + environment, serviceName, groupId, setup, }: { + environment?: string; serviceName: string; groupId?: string; setup: Setup & SetupTimeRange; }) { const bucketSize = getBucketSize({ start: setup.start, end: setup.end }); const { buckets, noHits } = await getBuckets({ + environment, serviceName, groupId, bucketSize, diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts index 121b9b3d0c46fb..0ab26f3c6e969d 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts @@ -11,16 +11,18 @@ import { TRANSACTION_SAMPLED, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getTransaction } from '../transactions/get_transaction'; export function getErrorGroupSample({ + environment, serviceName, groupId, setup, }: { + environment?: string; serviceName: string; groupId: string; setup: Setup & SetupTimeRange; @@ -39,7 +41,8 @@ export function getErrorGroupSample({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [ERROR_GROUP_ID]: groupId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], should: [{ term: { [TRANSACTION_SAMPLED]: true } }], diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index 6e91f8fe7cdd28..28d89eb0574709 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -21,11 +21,13 @@ import { getErrorName } from '../helpers/get_error_name'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; export function getErrorGroups({ + environment, serviceName, sortField, sortDirection = 'desc', setup, }: { + environment?: string; serviceName: string; sortField?: string; sortDirection?: 'asc' | 'desc'; @@ -37,7 +39,11 @@ export function getErrorGroups({ // sort buckets by last occurrence of error const sortByLatestOccurrence = sortField === 'latestOccurrenceAt'; - const projection = getErrorGroupsProjection({ setup, serviceName }); + const projection = getErrorGroupsProjection({ + environment, + setup, + serviceName, + }); const order: SortOptions = sortByLatestOccurrence ? { diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts index 424c85d55a36e1..71744c3e590929 100644 --- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts @@ -6,7 +6,7 @@ */ import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { rangeQuery } from '../../../../common/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { TRANSACTION_DURATION, @@ -35,7 +35,7 @@ export async function getHasAggregatedTransactions({ bool: { filter: [ { exists: { field: TRANSACTION_DURATION_HISTOGRAM } }, - ...(start && end ? [{ range: rangeFilter(start, end) }] : []), + ...(start && end ? rangeQuery(start, end) : []), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts deleted file mode 100644 index 57bf511f459428..00000000000000 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts +++ /dev/null @@ -1,32 +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 { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; -import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; - -describe('getEnvironmentUiFilterES', () => { - it('should return empty array, when environment is undefined', () => { - const uiFilterES = getEnvironmentUiFilterES(); - expect(uiFilterES).toHaveLength(0); - }); - - it('should create a filter for a service environment', () => { - const uiFilterES = getEnvironmentUiFilterES('test'); - expect(uiFilterES).toHaveLength(1); - expect(uiFilterES[0]).toHaveProperty(['term', SERVICE_ENVIRONMENT], 'test'); - }); - - it('should create a filter for missing service environments', () => { - const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED.value); - expect(uiFilterES).toHaveLength(1); - expect(uiFilterES[0]).toHaveProperty( - ['bool', 'must_not', 'exists', 'field'], - SERVICE_ENVIRONMENT - ); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts index 63e0edd6097bf4..e91c9b52deecf1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts +++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts @@ -7,7 +7,6 @@ import { ESFilter } from '../../../../../../typings/elasticsearch'; import { UIFilters } from '../../../../typings/ui_filters'; -import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; import { localUIFilters, localUIFilterNames, @@ -28,10 +27,7 @@ export function getEsFilter(uiFilters: UIFilters) { }; }) as ESFilter[]; - const esFilters = [ - ...getKueryUiFilterES(uiFilters.kuery), - ...getEnvironmentUiFilterES(uiFilters.environment), - ].concat(mappedFilters) as ESFilter[]; + const esFilters = [...getKueryUiFilterES(uiFilters.kuery), ...mappedFilters]; return esFilters; } diff --git a/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap index 961a1eee61d1d5..4eed09f3e5c28b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap @@ -71,6 +71,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -80,11 +85,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -159,6 +159,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -168,11 +173,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -322,6 +322,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -331,11 +336,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -415,6 +415,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -424,11 +429,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -498,6 +498,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -507,11 +512,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -601,15 +601,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -621,6 +612,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -695,15 +695,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -715,6 +706,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -864,15 +864,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -884,6 +875,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -963,15 +963,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -983,6 +974,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -1052,15 +1052,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -1072,6 +1063,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts index e03be2391597d4..c5e80600b69d43 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts @@ -9,13 +9,18 @@ import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getCPUChartData } from './shared/cpu'; import { getMemoryChartData } from './shared/memory'; -export async function getDefaultMetricsCharts( - setup: Setup & SetupTimeRange, - serviceName: string -) { +export async function getDefaultMetricsCharts({ + environment, + serviceName, + setup, +}: { + environment?: string; + serviceName: string; + setup: Setup & SetupTimeRange; +}) { const charts = await Promise.all([ - getCPUChartData({ setup, serviceName }), - getMemoryChartData({ setup, serviceName }), + getCPUChartData({ environment, setup, serviceName }), + getMemoryChartData({ environment, setup, serviceName }), ]); return { charts }; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index e76ad10b535b09..d7c9294c8ec7a1 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -22,12 +22,14 @@ import { getBucketSize } from '../../../../helpers/get_bucket_size'; import { getVizColorForIndex } from '../../../../../../common/viz_colors'; export async function fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, chartBase, fieldName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -39,6 +41,7 @@ export async function fetchAndTransformGcMetrics({ const { bucketSize } = getBucketSize({ start, end }); const projection = getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 7989f57046ae78..8c5b9fb3db9221 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -33,16 +33,19 @@ const chartBase: ChartBase = { }; function getGcRateChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_gc_rate_charts', () => fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts index 446894f82b75e3..98f31f06c1b644 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -33,16 +33,19 @@ const chartBase: ChartBase = { }; function getGcTimeChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_gc_time_charts', () => fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts index 2b7bb9ea8da6ed..d6cbc4a07e8f9b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts @@ -53,16 +53,19 @@ const chartBase: ChartBase = { }; export function getHeapMemoryChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_heap_memory_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts index e137720000262a..970b4d3499b798 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts @@ -16,23 +16,30 @@ import { getGcRateChart } from './gc/get_gc_rate_chart'; import { getGcTimeChart } from './gc/get_gc_time_chart'; export function getJavaMetricsCharts({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_java_system_metric_charts', async () => { const charts = await Promise.all([ - getCPUChartData({ setup, serviceName, serviceNodeName }), - getMemoryChartData({ setup, serviceName, serviceNodeName }), - getHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getNonHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getThreadCountChart({ setup, serviceName, serviceNodeName }), - getGcRateChart({ setup, serviceName, serviceNodeName }), - getGcTimeChart({ setup, serviceName, serviceNodeName }), + getCPUChartData({ environment, setup, serviceName, serviceNodeName }), + getMemoryChartData({ environment, setup, serviceName, serviceNodeName }), + getHeapMemoryChart({ environment, setup, serviceName, serviceNodeName }), + getNonHeapMemoryChart({ + environment, + setup, + serviceName, + serviceNodeName, + }), + getThreadCountChart({ environment, setup, serviceName, serviceNodeName }), + getGcRateChart({ environment, setup, serviceName, serviceNodeName }), + getGcTimeChart({ environment, setup, serviceName, serviceNodeName }), ]); return { charts }; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts index a3e253d2c81d67..25abd2c34c83a9 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts @@ -50,16 +50,19 @@ const chartBase: ChartBase = { }; export async function getNonHeapMemoryChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_non_heap_memory_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts index e176c156ad05a5..c8a209fee701a1 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts @@ -42,16 +42,19 @@ const chartBase: ChartBase = { }; export async function getThreadCountChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_thread_count_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts index e7f576b73c5aef..ebfe504e5269b7 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts @@ -54,16 +54,19 @@ const chartBase: ChartBase = { }; export function getCPUChartData({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_cpu_metric_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index 0f7954d86d3e2c..55b3328bcd2a9b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -71,10 +71,12 @@ export const percentCgroupMemoryUsedScript = { }; export async function getMemoryChartData({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -84,6 +86,7 @@ export async function getMemoryChartData({ 'get_cgroup_memory_metrics_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, @@ -101,6 +104,7 @@ export async function getMemoryChartData({ if (cgroupResponse.noHits) { return await withApmSpan('get_system_memory_metrics_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 7a52806601e0e8..17e9aef29ba82d 100644 --- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -48,6 +48,7 @@ interface Filter { } export async function fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, @@ -55,6 +56,7 @@ export async function fetchAndTransformMetrics({ aggs, additionalFilters = [], }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -65,6 +67,7 @@ export async function fetchAndTransformMetrics({ const { start, end, apmEventClient, config } = setup; const projection = getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts index 5083982f1cb9cb..eda71ef380ee9c 100644 --- a/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts @@ -15,11 +15,13 @@ export interface MetricsChartsByAgentAPIResponse { } export async function getMetricsChartDataByAgent({ + environment, setup, serviceName, serviceNodeName, agentName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -27,11 +29,16 @@ export async function getMetricsChartDataByAgent({ }): Promise { switch (agentName) { case 'java': { - return getJavaMetricsCharts({ setup, serviceName, serviceNodeName }); + return getJavaMetricsCharts({ + environment, + setup, + serviceName, + serviceNodeName, + }); } default: { - return getDefaultMetricsCharts(setup, serviceName); + return getDefaultMetricsCharts({ environment, setup, serviceName }); } } } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts index 4af57a685bf839..c7ac678899b58c 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts @@ -6,7 +6,7 @@ */ import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; @@ -36,7 +36,7 @@ export function getServiceCount({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }], + filter: rangeQuery(start, end), }, }, aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } }, diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts index 87394567afc50b..2da4b0f8de3632 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Coordinates } from '../../../../observability/typings/common'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; @@ -38,7 +38,7 @@ export function getTransactionCoordinates({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }], + filter: rangeQuery(start, end), }, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts index 368c5ec5463592..9626019347e5bb 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; export async function hasRumData({ setup }: { setup: Setup & SetupTimeRange }) { @@ -31,9 +31,7 @@ export async function hasRumData({ setup }: { setup: Setup & SetupTimeRange }) { }, aggs: { services: { - filter: { - range: rangeFilter(start, end), - }, + filter: rangeQuery(start, end)[0], aggs: { mostTraffic: { terms: { diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts index 939ebbb1f79419..259a0e6daea6fc 100644 --- a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { ProcessorEvent } from '../../../common/processor_event'; import { TRACE_ID } from '../../../common/elasticsearch_fieldnames'; import { @@ -42,7 +42,7 @@ export async function fetchServicePathsFromTraceIds( [TRACE_ID]: traceIds, }, }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 2c64678eb082ec..ab221e30ea4897 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -17,6 +17,7 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; +import { rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -51,16 +52,11 @@ export async function getServiceAnomalies({ bool: { filter: [ { terms: { result_type: ['model_plot', 'record'] } }, - { - range: { - timestamp: { - // fetch data for at least 30 minutes - gte: Math.min(end - 30 * 60 * 1000, start), - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery( + Math.min(end - 30 * 60 * 1000, start), + end, + 'timestamp' + ), { terms: { // Only retrieving anomalies for transaction types "request" and "page-load" diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts index 951484308db195..1aee1bb5b242af 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts @@ -15,8 +15,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getServicesProjection } from '../../projections/services'; import { mergeProjection } from '../../projections/util/merge_projection'; +import { environmentQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { DEFAULT_ANOMALIES, @@ -88,14 +88,17 @@ async function getConnectionData({ async function getServicesData(options: IEnvOptions) { return withApmSpan('get_service_stats_for_service_map', async () => { - const { setup, searchAggregatedTransactions } = options; + const { environment, setup, searchAggregatedTransactions } = options; const projection = getServicesProjection({ setup: { ...setup, esFilter: [] }, searchAggregatedTransactions, }); - let { filter } = projection.body.query.bool; + let filter = [ + ...projection.body.query.bool.filter, + ...environmentQuery(environment), + ]; if (options.serviceName) { filter = filter.concat({ @@ -105,10 +108,6 @@ async function getServicesData(options: IEnvOptions) { }); } - if (options.environment) { - filter = filter.concat(getEnvironmentUiFilterES(options.environment)); - } - const params = mergeProjection(projection, { body: { size: 0, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts index ef4faf94063461..6e9225041b199c 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts @@ -6,7 +6,10 @@ */ import { find, uniqBy } from 'lodash'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from '../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, @@ -27,8 +30,10 @@ export function getConnections({ if (!paths) { return []; } + const isEnvironmentSelected = + environment && environment !== ENVIRONMENT_ALL.value; - if (serviceName || environment) { + if (serviceName || isEnvironmentSelected) { paths = paths.filter((path) => { return ( path diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts index 63f28abab8f3aa..b161345e729d35 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts @@ -19,11 +19,13 @@ describe('getServiceMapServiceNodeInfo', () => { hits: { total: { value: 0 } }, }), }, + esFilter: [], indices: {}, - uiFilters: { environment: 'test environment' }, + uiFilters: {}, } as unknown) as Setup & SetupTimeRange; const serviceName = 'test service name'; const result = await getServiceMapServiceNodeInfo({ + environment: 'test environment', setup, serviceName, searchAggregatedTransactions: false, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 213702bf06f4ce..e384b15685dad4 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -19,14 +19,13 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../helpers/aggregated_transactions'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { percentCgroupMemoryUsedScript, @@ -51,6 +50,7 @@ interface TaskParameters { } export function getServiceMapServiceNodeInfo({ + environment, serviceName, setup, searchAggregatedTransactions, @@ -59,9 +59,9 @@ export function getServiceMapServiceNodeInfo({ const { start, end, uiFilters } = setup; const filter: ESFilter[] = [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(uiFilters.environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ]; const minutes = Math.abs((end - start) / (1000 * 60)); @@ -106,16 +106,13 @@ async function getErrorStats({ searchAggregatedTransactions: boolean; }) { return withApmSpan('get_error_rate_for_service_map_node', async () => { - const setupWithBlankUiFilters = { - ...setup, - uiFilters: { environment }, - esFilter: getEnvironmentUiFilterES(environment), - }; const { noHits, average } = await getErrorRate({ - setup: setupWithBlankUiFilters, + environment, + setup, serviceName, searchAggregatedTransactions, }); + return { avgErrorRate: noHits ? null : average }; }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index deb9104a839051..e8dcb28baa9a38 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -16,9 +16,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; const MAX_TRACES_TO_INSPECT = 1000; @@ -35,8 +34,6 @@ export function getTraceSampleIds({ return withApmSpan('get_trace_sample_ids', async () => { const { start, end, apmEventClient, config } = setup; - const rangeQuery = { range: rangeFilter(start, end) }; - const query = { bool: { filter: [ @@ -45,7 +42,7 @@ export function getTraceSampleIds({ field: SPAN_DESTINATION_SERVICE_RESOURCE, }, }, - rangeQuery, + ...rangeQuery(start, end), ] as ESFilter[], }, } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } }; @@ -54,7 +51,7 @@ export function getTraceSampleIds({ query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } }); } - query.bool.filter.push(...getEnvironmentUiFilterES(environment)); + query.bool.filter.push(...environmentQuery(environment)); const fingerprintBucketSize = serviceName ? config['xpack.apm.serviceMapFingerprintBucketSize'] diff --git a/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap index d83e558775be40..e6d702cc03c0b1 100644 --- a/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap @@ -35,6 +35,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -44,11 +49,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -97,15 +97,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -117,6 +108,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 606ce870351564..3e68831ee7cba7 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -101,14 +101,6 @@ Array [ "aggs": Object { "transactionType": Object { "aggs": Object { - "agentName": Object { - "top_hits": Object { - "docvalue_fields": Array [ - "agent.name", - ], - "size": 1, - }, - }, "avg_duration": Object { "avg": Object { "field": "transaction.duration.us", @@ -129,6 +121,16 @@ Array [ ], }, }, + "sample": Object { + "top_metrics": Object { + "metrics": Object { + "field": "agent.name", + }, + "sort": Object { + "@timestamp": "desc", + }, + }, + }, "timeseries": Object { "aggs": Object { "avg_duration": Object { diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 7c746aac29af92..67aa9d7fcd8707 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -12,13 +12,12 @@ import { SERVICE_NAME, SERVICE_VERSION, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getDerivedServiceAnnotations({ @@ -40,7 +39,7 @@ export async function getDerivedServiceAnnotations({ ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...getEnvironmentUiFilterES(environment), + ...environmentQuery(environment), ]; const versions = @@ -57,7 +56,7 @@ export async function getDerivedServiceAnnotations({ size: 0, query: { bool: { - filter: [...filter, { range: rangeFilter(start, end) }], + filter: [...filter, ...rangeQuery(start, end)], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index fc53f763dac0ff..6c7cbc26ea6536 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -5,19 +5,18 @@ * 2.0. */ -import { ElasticsearchClient, Logger } from 'kibana/server'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { ElasticsearchClient, Logger } from 'kibana/server'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { unwrapEsResponse, WrappedElasticsearchClientError, } from '../../../../../observability/server'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { Annotation as ESAnnotation } from '../../../../../observability/common/annotations'; import { ScopedAnnotationsClient } from '../../../../../observability/server'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -37,18 +36,18 @@ export function getStoredAnnotations({ logger: Logger; }): Promise { return withApmSpan('get_stored_annotations', async () => { + const { start, end } = setup; + const body = { size: 50, query: { bool: { filter: [ - { - range: rangeFilter(setup.start, setup.end), - }, { term: { 'annotation.type': 'deployment' } }, { term: { tags: 'apm' } }, { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts index 29c77da6e4075f..3683a069342a9b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -10,7 +10,7 @@ import { AGENT_NAME, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -44,7 +44,7 @@ export function getServiceAgentName({ bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index ef2b50cbdbedfe..558d6ae22f00f5 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -19,9 +19,8 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -33,7 +32,7 @@ export const getDestinationMap = ({ }: { setup: Setup & SetupTimeRange; serviceName: string; - environment: string; + environment?: string; }) => { return withApmSpan('get_service_destination_map', async () => { const { start, end, apmEventClient } = setup; @@ -50,8 +49,8 @@ export const getDestinationMap = ({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, @@ -71,14 +70,13 @@ export const getDestinationMap = ({ ], }, aggs: { - docs: { - top_hits: { - docvalue_fields: [ - SPAN_TYPE, - SPAN_SUBTYPE, - SPAN_ID, + sample: { + top_metrics: { + metrics: [ + { field: SPAN_TYPE }, + { field: SPAN_SUBTYPE }, + { field: SPAN_ID }, ] as const, - _source: false, sort: { '@timestamp': 'desc', }, @@ -93,15 +91,15 @@ export const getDestinationMap = ({ const outgoingConnections = response.aggregations?.connections.buckets.map((bucket) => { - const doc = bucket.docs.hits.hits[0]; + const fieldValues = bucket.sample.top[0].metrics; return { [SPAN_DESTINATION_SERVICE_RESOURCE]: String( bucket.key[SPAN_DESTINATION_SERVICE_RESOURCE] ), - [SPAN_ID]: String(doc.fields[SPAN_ID]?.[0]), - [SPAN_TYPE]: String(doc.fields[SPAN_TYPE]?.[0] ?? ''), - [SPAN_SUBTYPE]: String(doc.fields[SPAN_SUBTYPE]?.[0] ?? ''), + [SPAN_ID]: (fieldValues[SPAN_ID] ?? '') as string, + [SPAN_TYPE]: (fieldValues[SPAN_TYPE] ?? '') as string, + [SPAN_SUBTYPE]: (fieldValues[SPAN_SUBTYPE] ?? '') as string, }; }) ?? []; @@ -123,7 +121,7 @@ export const getDestinationMap = ({ ), }, }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts index 9a020daa7e095d..dfbdfb3f504e8c 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts @@ -13,9 +13,8 @@ import { SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -29,7 +28,7 @@ export const getMetrics = ({ }: { setup: Setup & SetupTimeRange; serviceName: string; - environment: string; + environment?: string; numBuckets: number; }) => { return withApmSpan('get_service_destination_metrics', async () => { @@ -49,8 +48,8 @@ export const getMetrics = ({ { exists: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT }, }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts index 19f306f5cb8035..724b5278d7edfc 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts @@ -51,7 +51,7 @@ export function getServiceDependencies({ }: { serviceName: string; setup: Setup & SetupTimeRange; - environment: string; + environment?: string; numBuckets: number; }): Promise { return withApmSpan('get_service_dependencies', async () => { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts index f5aa01e1dfa58a..a17fb6da2007fa 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts @@ -9,7 +9,7 @@ import { ValuesType } from 'utility-types'; import { orderBy } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { ERROR_EXC_MESSAGE, @@ -28,6 +28,7 @@ export type ServiceErrorGroupItem = ValuesType< >; export async function getServiceErrorGroups({ + environment, serviceName, setup, size, @@ -37,6 +38,7 @@ export async function getServiceErrorGroups({ sortField, transactionType, }: { + environment?: string; serviceName: string; setup: Setup & SetupTimeRange; size: number; @@ -63,7 +65,8 @@ export async function getServiceErrorGroups({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, @@ -145,7 +148,8 @@ export async function getServiceErrorGroups({ { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } }, { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts index 4f8088352d0ae2..ef90e5197229b9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts @@ -6,7 +6,7 @@ */ import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { METRIC_CGROUP_MEMORY_USAGE_BYTES, @@ -26,6 +26,7 @@ import { import { withApmSpan } from '../../../utils/with_apm_span'; export async function getServiceInstanceSystemMetricStats({ + environment, setup, serviceName, size, @@ -95,8 +96,9 @@ export async function getServiceInstanceSystemMetricStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter], diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts index 2cbe5a42206d18..620fd9828bd37f 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts @@ -6,7 +6,7 @@ */ import { EventOutcome } from '../../../../common/event_outcome'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { EVENT_OUTCOME, @@ -22,8 +22,14 @@ import { } from '../../helpers/aggregated_transactions'; import { calculateThroughput } from '../../helpers/calculate_throughput'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { + getLatencyAggregation, + getLatencyValue, +} from '../../helpers/latency_aggregation_type'; export async function getServiceInstanceTransactionStats({ + environment, + latencyAggregationType, setup, transactionType, serviceName, @@ -45,11 +51,7 @@ export async function getServiceInstanceTransactionStats({ ); const subAggs = { - avg_transaction_duration: { - avg: { - field, - }, - }, + ...getLatencyAggregation(latencyAggregationType, field), failures: { filter: { term: { @@ -72,9 +74,10 @@ export async function getServiceInstanceTransactionStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, @@ -115,7 +118,7 @@ export async function getServiceInstanceTransactionStats({ (serviceNodeBucket) => { const { doc_count: count, - avg_transaction_duration: avgTransactionDuration, + latency, key, failures, timeseries, @@ -138,10 +141,16 @@ export async function getServiceInstanceTransactionStats({ })), }, latency: { - value: avgTransactionDuration.value, + value: getLatencyValue({ + aggregation: latency, + latencyAggregationType, + }), timeseries: timeseries.buckets.map((dateBucket) => ({ x: dateBucket.key, - y: dateBucket.avg_transaction_duration.value, + y: getLatencyValue({ + aggregation: dateBucket.latency, + latencyAggregationType, + }), })), }, }; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts index 021774f9522c1b..7c0124f4ce0040 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -12,6 +13,8 @@ import { getServiceInstanceSystemMetricStats } from './get_service_instance_syst import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats'; export interface ServiceInstanceParams { + environment?: string; + latencyAggregationType: LatencyAggregationType; setup: Setup & SetupTimeRange; serviceName: string; transactionType: string; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts index f1198a4d858fda..5c43191cf588c2 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts @@ -21,7 +21,7 @@ import { SERVICE_VERSION, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -74,7 +74,7 @@ export function getServiceMetadataDetails({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts index 0ea95a08abaa98..b342ffea02464e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts @@ -16,7 +16,7 @@ import { HOST_OS_PLATFORM, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -55,7 +55,7 @@ export function getServiceMetadataIcons({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts index 54cf89d6125b6e..ce36db3e82babe 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts @@ -14,7 +14,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { Coordinate } from '../../../typings/timeseries'; import { withApmSpan } from '../../utils/with_apm_span'; import { @@ -31,6 +31,7 @@ import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate'; export async function getServiceTransactionGroupComparisonStatistics({ + environment, serviceName, transactionNames, setup, @@ -39,6 +40,7 @@ export async function getServiceTransactionGroupComparisonStatistics({ transactionType, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionNames: string[]; setup: Setup & SetupTimeRange; @@ -82,10 +84,11 @@ export async function getServiceTransactionGroupComparisonStatistics({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts index 168eed8e38374c..ddbfd617faf65b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts @@ -13,7 +13,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -36,12 +36,14 @@ export type ServiceOverviewTransactionGroupSortField = | 'impact'; export async function getServiceTransactionGroups({ + environment, serviceName, setup, searchAggregatedTransactions, transactionType, latencyAggregationType, }: { + environment?: string; serviceName: string; setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; @@ -70,10 +72,11 @@ export async function getServiceTransactionGroups({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts index bc4660e2c01a5e..3d77bf5bd6baf3 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -9,7 +9,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -46,7 +46,7 @@ export function getServiceTransactionTypes({ searchAggregatedTransactions ), { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts index e88fafb061912d..6fc868b0f0a4ec 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts @@ -8,28 +8,25 @@ import { getSeverity } from '../../../../common/anomaly_detection'; import { getServiceHealthStatus } from '../../../../common/service_health_status'; import { getServiceAnomalies } from '../../service_map/get_service_anomalies'; -import { - ServicesItemsProjection, - ServicesItemsSetup, -} from './get_services_items'; +import { ServicesItemsSetup } from './get_services_items'; interface AggregationParams { + environment?: string; setup: ServicesItemsSetup; - projection: ServicesItemsProjection; searchAggregatedTransactions: boolean; } -export const getHealthStatuses = async ( - { setup }: AggregationParams, - mlAnomaliesEnvironment?: string -) => { +export const getHealthStatuses = async ({ + environment, + setup, +}: AggregationParams) => { if (!setup.ml) { return []; } const anomalies = await getServiceAnomalies({ setup, - environment: mlAnomaliesEnvironment, + environment, }); return anomalies.serviceAnomalies.map((anomalyStats) => { diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index 36540b01a07cc9..e1f8bca83829cc 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -15,7 +15,7 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../../common/transaction_types'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -32,6 +32,7 @@ import { ServicesItemsSetup } from './get_services_items'; import { withApmSpan } from '../../../utils/with_apm_span'; interface AggregationParams { + environment?: string; setup: ServicesItemsSetup; searchAggregatedTransactions: boolean; } @@ -39,6 +40,7 @@ interface AggregationParams { const MAX_NUMBER_OF_SERVICES = 500; export async function getServiceTransactionStats({ + environment, setup, searchAggregatedTransactions, }: AggregationParams) { @@ -71,11 +73,12 @@ export async function getServiceTransactionStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, - ...esFilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ], }, }, @@ -98,10 +101,12 @@ export async function getServiceTransactionStats({ missing: '', }, }, - agentName: { - top_hits: { - docvalue_fields: [AGENT_NAME] as const, - size: 1, + sample: { + top_metrics: { + metrics: { field: AGENT_NAME } as const, + sort: { + '@timestamp': 'desc', + }, }, }, timeseries: { @@ -139,9 +144,9 @@ export async function getServiceTransactionStats({ environments: topTransactionTypeBucket.environments.buckets .map((environmentBucket) => environmentBucket.key as string) .filter(Boolean), - agentName: topTransactionTypeBucket.agentName.hits.hits[0].fields[ - 'agent.name' - ]?.[0] as AgentName, + agentName: topTransactionTypeBucket.sample.top[0].metrics[ + AGENT_NAME + ] as AgentName, avgResponseTime: { value: topTransactionTypeBucket.avg_duration.value, timeseries: topTransactionTypeBucket.timeseries.buckets.map( diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts index 1ba9aaf980201f..c2677af038486b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -14,19 +14,21 @@ import { getHealthStatuses } from './get_health_statuses'; import { getServiceTransactionStats } from './get_service_transaction_stats'; export type ServicesItemsSetup = Setup & SetupTimeRange; -export type ServicesItemsProjection = ReturnType; export async function getServicesItems({ + environment, setup, searchAggregatedTransactions, logger, }: { + environment?: string; setup: ServicesItemsSetup; searchAggregatedTransactions: boolean; logger: Logger; }) { return withApmSpan('get_services_items', async () => { const params = { + environment, projection: getServicesProjection({ setup, searchAggregatedTransactions, @@ -37,7 +39,7 @@ export async function getServicesItems({ const [transactionStats, healthStatuses] = await Promise.all([ getServiceTransactionStats(params), - getHealthStatuses(params, setup.uiFilters.environment).catch((err) => { + getHealthStatuses(params).catch((err) => { logger.error(err); return []; }), diff --git a/x-pack/plugins/apm/server/lib/services/get_services/index.ts b/x-pack/plugins/apm/server/lib/services/get_services/index.ts index 45efd80c45a980..1a0ddeda11651b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/index.ts @@ -14,10 +14,12 @@ import { getServicesItems } from './get_services_items'; import { hasHistoricalAgentData } from './has_historical_agent_data'; export async function getServices({ + environment, setup, searchAggregatedTransactions, logger, }: { + environment?: string; setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; logger: Logger; @@ -25,6 +27,7 @@ export async function getServices({ return withApmSpan('get_services', async () => { const [items, hasLegacyData] = await Promise.all([ getServicesItems({ + environment, setup, searchAggregatedTransactions, logger, diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index 33268e9b3332d7..f7cd23b0e37a7b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -10,7 +10,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -20,6 +20,7 @@ import { Setup } from '../helpers/setup_request'; import { withApmSpan } from '../../utils/with_apm_span'; interface Options { + environment?: string; searchAggregatedTransactions: boolean; serviceName: string; setup: Setup; @@ -29,6 +30,7 @@ interface Options { } function fetcher({ + environment, searchAggregatedTransactions, serviceName, setup, @@ -36,16 +38,17 @@ function fetcher({ start, end, }: Options) { - const { apmEventClient } = setup; + const { esFilter, apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts index bd3ecf1e0f862d..f631657f872761 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts @@ -15,7 +15,7 @@ import { ERROR_LOG_LEVEL, } from '../../../common/elasticsearch_fieldnames'; import { APMError } from '../../../typings/es_schemas/ui/apm_error'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseValueType } from '../../../typings/common'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -44,7 +44,7 @@ export async function getTraceItems( bool: { filter: [ { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } }, }, @@ -74,7 +74,7 @@ export async function getTraceItems( bool: { filter: [ { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], should: { exists: { field: PARENT_ID }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 89069d74bacf8b..7fb2bb2fcbeeb1 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -13,11 +13,13 @@ Array [ "transaction_groups": Object { "aggs": Object { "transaction_type": Object { - "top_hits": Object { - "_source": Array [ - "transaction.type", - ], - "size": 1, + "top_metrics": Object { + "metrics": Object { + "field": "transaction.type", + }, + "sort": Object { + "@timestamp": "desc", + }, }, }, }, @@ -222,11 +224,13 @@ Array [ "transaction_groups": Object { "aggs": Object { "transaction_type": Object { - "top_hits": Object { - "_source": Array [ - "transaction.type", - ], - "size": 1, + "top_metrics": Object { + "metrics": Object { + "field": "transaction.type", + }, + "sort": Object { + "@timestamp": "desc", + }, }, }, }, @@ -240,12 +244,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -254,8 +254,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -295,12 +299,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -309,8 +309,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -350,12 +354,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -364,8 +364,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -411,12 +415,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -425,8 +425,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 0fad948edde19b..09e5e358a1b7c1 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -27,6 +27,7 @@ import { } from './get_transaction_group_stats'; interface TopTransactionOptions { + environment?: string; type: 'top_transactions'; serviceName: string; transactionType: string; @@ -35,6 +36,7 @@ interface TopTransactionOptions { } interface TopTraceOptions { + environment?: string; type: 'top_traces'; transactionName?: string; searchAggregatedTransactions: boolean; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index 839efc9009c389..d1a056002db078 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -14,7 +14,7 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -29,12 +29,14 @@ import { import { withApmSpan } from '../../utils/with_apm_span'; export async function getErrorRate({ + environment, serviceName, transactionType, transactionName, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionType?: string; transactionName?: string; @@ -57,17 +59,18 @@ export async function getErrorRate({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, { terms: { [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success], }, }, + ...transactionNamefilter, + ...transactionTypefilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...transactionNamefilter, - ...transactionTypefilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index ee19a6a8d15918..5ee46bf1a5918a 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -69,9 +69,13 @@ export function getCounts({ request, setup }: MetricParams) { return withApmSpan('get_transaction_group_transaction_count', async () => { const params = mergeRequestWithAggs(request, { transaction_type: { - top_hits: { - size: 1, - _source: [TRANSACTION_TYPE], + top_metrics: { + sort: { + '@timestamp': 'desc', + }, + metrics: { + field: TRANSACTION_TYPE, + } as const, }, }, }); @@ -81,14 +85,12 @@ export function getCounts({ request, setup }: MetricParams) { return arrayUnionToCallable( response.aggregations?.transaction_groups.buckets ?? [] ).map((bucket) => { - // type is Transaction | APMBaseDoc because it could be a metric document - const source = (bucket.transaction_type.hits.hits[0] - ._source as unknown) as { transaction: { type: string } }; - return { key: bucket.key as BucketKey, count: bucket.doc_count, - transactionType: source.transaction.type, + transactionType: bucket.transaction_type.top[0].metrics[ + TRANSACTION_TYPE + ] as string, }; }); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index 8a2579b4a2b875..c3741184c807db 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -18,18 +18,20 @@ import { TRANSACTION_BREAKDOWN_COUNT, } from '../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; import { withApmSpan } from '../../../utils/with_apm_span'; export function getTransactionBreakdown({ + environment, setup, serviceName, transactionName, transactionType, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; transactionName?: string; @@ -82,7 +84,8 @@ export function getTransactionBreakdown({ const filters = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 22a34ded4c20d5..7ed016cd4b4c61 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -17,7 +17,10 @@ import { } from '../../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { joinByKey } from '../../../../../common/utils/join_by_key'; -import { rangeFilter } from '../../../../../common/utils/range_filter'; +import { + environmentQuery, + rangeQuery, +} from '../../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -46,6 +49,7 @@ function getHistogramAggOptions({ } export async function getBuckets({ + environment, serviceName, transactionName, transactionType, @@ -56,6 +60,7 @@ export async function getBuckets({ setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -75,7 +80,8 @@ export async function getBuckets({ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [TRANSACTION_NAME]: transactionName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; @@ -109,8 +115,11 @@ export async function getBuckets({ }), aggs: { samples: { - top_hits: { - _source: [TRANSACTION_ID, TRACE_ID], + top_metrics: { + metrics: [ + { field: TRANSACTION_ID }, + { field: TRACE_ID }, + ] as const, size: 10, sort: { _score: 'desc', @@ -128,9 +137,9 @@ export async function getBuckets({ response.aggregations?.distribution.buckets.map((bucket) => { return { key: bucket.key, - samples: bucket.samples.hits.hits.map((hit) => ({ - traceId: hit._source.trace.id, - transactionId: hit._source.transaction.id, + samples: bucket.samples.top.map((sample) => ({ + traceId: sample.metrics[TRACE_ID] as string, + transactionId: sample.metrics[TRANSACTION_ID] as string, })), }; }) ?? [] diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index ed54dae32704e3..f8061ea9894698 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -15,15 +15,18 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getDistributionMax({ + environment, serviceName, transactionName, transactionType, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -49,15 +52,8 @@ export async function getDistributionMax({ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [TRANSACTION_NAME]: transactionName } }, - { - range: { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts index 22436cac40183d..92d1d96b4a8e3a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts @@ -20,6 +20,7 @@ function getBucketSize(max: number) { } export async function getTransactionDistribution({ + environment, serviceName, transactionName, transactionType, @@ -28,6 +29,7 @@ export async function getTransactionDistribution({ setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -38,6 +40,7 @@ export async function getTransactionDistribution({ }) { return withApmSpan('get_transaction_latency_distribution', async () => { const distributionMax = await getDistributionMax({ + environment, serviceName, transactionName, transactionType, @@ -52,6 +55,7 @@ export async function getTransactionDistribution({ const bucketSize = getBucketSize(distributionMax); const { buckets, noHits } = await getBuckets({ + environment, serviceName, transactionName, transactionType, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index 002ddd1ec35f04..d566f3a169e78a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -7,6 +7,7 @@ import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; +import { rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; @@ -40,15 +41,7 @@ export function anomalySeriesFetcher({ { terms: { result_type: ['model_plot', 'record'] } }, { term: { partition_field_value: serviceName } }, { term: { by_field_value: transactionType } }, - { - range: { - timestamp: { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery(start, end, 'timestamp'), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts index 29dd562330cc15..a03b1ac82e90a6 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts @@ -18,20 +18,21 @@ import { ANOMALY_THRESHOLD } from '../../../../../ml/common'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getAnomalySeries({ + environment, serviceName, transactionType, transactionName, setup, logger, }: { + environment?: string; serviceName: string; transactionType: string; transactionName?: string; setup: Setup & SetupTimeRange; logger: Logger; }) { - const { uiFilters, start, end, ml } = setup; - const { environment } = uiFilters; + const { start, end, ml } = setup; // don't fetch anomalies if the ML plugin is not setup if (!ml) { @@ -45,18 +46,17 @@ export async function getAnomalySeries({ } // don't fetch anomalies when no specific environment is selected - if (environment === ENVIRONMENT_ALL.value) { + if (!environment || environment === ENVIRONMENT_ALL.value) { return undefined; } - // don't fetch anomalies if unknown uiFilters are applied - const knownFilters = ['environment', 'serviceName']; - const hasUnknownFiltersApplied = Object.entries(setup.uiFilters) - .filter(([key, value]) => !!value) - .map(([key]) => key) - .some((uiFilterName) => !knownFilters.includes(uiFilterName)); + // Don't fetch anomalies if uiFilters are applied. This filters out anything + // with empty values so `kuery: ''` returns false but `kuery: 'x:y'` returns true. + const hasUiFiltersApplied = + Object.entries(setup.uiFilters).filter(([_key, value]) => !!value).length > + 0; - if (hasUnknownFiltersApplied) { + if (hasUiFiltersApplied) { return undefined; } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index ee27d00fdc0d4d..e1d3921d298c77 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -13,7 +13,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -31,6 +31,7 @@ export type LatencyChartsSearchResponse = PromiseReturnType< >; function searchLatency({ + environment, serviceName, transactionType, transactionName, @@ -38,6 +39,7 @@ function searchLatency({ searchAggregatedTransactions, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; @@ -45,16 +47,17 @@ function searchLatency({ searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; }) { - const { start, end, apmEventClient } = setup; + const { esFilter, start, end, apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (transactionName) { @@ -102,6 +105,7 @@ function searchLatency({ } export function getLatencyTimeseries({ + environment, serviceName, transactionType, transactionName, @@ -109,6 +113,7 @@ export function getLatencyTimeseries({ searchAggregatedTransactions, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; @@ -118,6 +123,7 @@ export function getLatencyTimeseries({ }) { return withApmSpan('get_latency_charts', async () => { const response = await searchLatency({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index 7c1fa9c3a2368e..ec5dbf0eab3e9e 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -13,7 +13,7 @@ import { TRANSACTION_RESULT, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -28,6 +28,7 @@ export type ThroughputChartsResponse = PromiseReturnType< >; function searchThroughput({ + environment, serviceName, transactionType, transactionName, @@ -35,6 +36,7 @@ function searchThroughput({ searchAggregatedTransactions, intervalString, }: { + environment?: string; serviceName: string; transactionType: string; transactionName: string | undefined; @@ -42,16 +44,17 @@ function searchThroughput({ searchAggregatedTransactions: boolean; intervalString: string; }) { - const { start, end, apmEventClient } = setup; + const { esFilter, start, end, apmEventClient } = setup; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + { term: { [TRANSACTION_TYPE]: transactionType } }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - { term: { [TRANSACTION_TYPE]: transactionType } }, - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (transactionName) { @@ -91,12 +94,14 @@ function searchThroughput({ } export async function getThroughputCharts({ + environment, serviceName, transactionType, transactionName, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionType: string; transactionName: string | undefined; @@ -107,6 +112,7 @@ export async function getThroughputCharts({ const { bucketSize, intervalString } = getBucketSize(setup); const response = await searchThroughput({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index 2fdb8e25fd9962..38d6b593dc72dc 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -9,7 +9,7 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { rangeQuery } from '../../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -37,7 +37,7 @@ export function getTransaction({ filter: [ { term: { [TRANSACTION_ID]: transactionId } }, { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/projections/errors.ts b/x-pack/plugins/apm/server/projections/errors.ts index 082fd53a0ca93d..342d78608efbf6 100644 --- a/x-pack/plugins/apm/server/projections/errors.ts +++ b/x-pack/plugins/apm/server/projections/errors.ts @@ -10,13 +10,15 @@ import { SERVICE_NAME, ERROR_GROUP_ID, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; export function getErrorGroupsProjection({ + environment, setup, serviceName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; }) { @@ -31,7 +33,8 @@ export function getErrorGroupsProjection({ bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/projections/metrics.ts b/x-pack/plugins/apm/server/projections/metrics.ts index f6c3f85ed48075..a32c2ae46c870b 100644 --- a/x-pack/plugins/apm/server/projections/metrics.ts +++ b/x-pack/plugins/apm/server/projections/metrics.ts @@ -10,7 +10,7 @@ import { SERVICE_NAME, SERVICE_NODE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../common/service_nodes'; import { ProcessorEvent } from '../../common/processor_event'; @@ -27,10 +27,12 @@ function getServiceNodeNameFilters(serviceNodeName?: string) { } export function getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -39,8 +41,9 @@ export function getMetricsProjection({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, ...getServiceNodeNameFilters(serviceNodeName), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index ff8d868bc4abe5..1d5f7316b69ad6 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, SERVICE_LANGUAGE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; @@ -28,7 +28,7 @@ export function getRumPageLoadTransactionsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, ...(checkFetchStartFieldExists ? [ @@ -79,7 +79,7 @@ export function getRumErrorsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), { term: { [AGENT_NAME]: 'rum-js' } }, { term: { diff --git a/x-pack/plugins/apm/server/projections/services.ts b/x-pack/plugins/apm/server/projections/services.ts index 33ffb45a006378..a9f5a7efd0e67e 100644 --- a/x-pack/plugins/apm/server/projections/services.ts +++ b/x-pack/plugins/apm/server/projections/services.ts @@ -7,7 +7,7 @@ import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; import { getProcessorEventForAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; @@ -34,7 +34,7 @@ export function getServicesProjection({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }, ...esFilter], + filter: [...rangeQuery(start, end), ...esFilter], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/projections/transactions.ts b/x-pack/plugins/apm/server/projections/transactions.ts index 76f2fc164e3fd2..45ed5d2865a67e 100644 --- a/x-pack/plugins/apm/server/projections/transactions.ts +++ b/x-pack/plugins/apm/server/projections/transactions.ts @@ -11,19 +11,21 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { getProcessorEventForAggregatedTransactions, getDocumentTypeFilterForAggregatedTransactions, } from '../lib/helpers/aggregated_transactions'; export function getTransactionsProjection({ + environment, setup, serviceName, transactionName, transactionType, searchAggregatedTransactions, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName?: string; transactionName?: string; @@ -44,14 +46,15 @@ export function getTransactionsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...serviceNameFilter, ...transactionNameFilter, ...transactionTypeFilter, - ...serviceNameFilter, - ...esFilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ], }; diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index 3c2ff00153ce79..d4a0db3c0d6c7b 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -13,7 +13,7 @@ import { getCorrelationsForFailedTransactions } from '../lib/correlations/get_co import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { createRoute } from './create_route'; -import { rangeRt } from './default_api_types'; +import { environmentRt, rangeRt } from './default_api_types'; const INVALID_LICENSE = i18n.translate( 'xpack.apm.significanTerms.license.text', @@ -37,6 +37,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ fieldNames: t.string, }), t.partial({ uiFilters: t.string }), + environmentRt, rangeRt, ]), }), @@ -47,6 +48,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ } const setup = await setupRequest(context, request); const { + environment, serviceName, transactionType, transactionName, @@ -55,6 +57,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ } = context.params.query; return getCorrelationsForSlowTransactions({ + environment, serviceName, transactionType, transactionName, @@ -78,6 +81,7 @@ export const correlationsForFailedTransactionsRoute = createRoute({ fieldNames: t.string, }), t.partial({ uiFilters: t.string }), + environmentRt, rangeRt, ]), }), @@ -88,14 +92,15 @@ export const correlationsForFailedTransactionsRoute = createRoute({ } const setup = await setupRequest(context, request); const { + environment, serviceName, transactionType, transactionName, - fieldNames, } = context.params.query; return getCorrelationsForFailedTransactions({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index fc5d6a3dd0bcdb..822a45fca269fd 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -63,8 +63,8 @@ import { transactionChartsErrorRateRoute, transactionGroupsRoute, transactionGroupsPrimaryStatisticsRoute, - transactionLatencyChatsRoute, - transactionThroughputChatsRoute, + transactionLatencyChartsRoute, + transactionThroughputChartsRoute, transactionGroupsComparisonStatisticsRoute, } from './transactions'; import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; @@ -167,8 +167,8 @@ const createApmApi = () => { .add(transactionChartsErrorRateRoute) .add(transactionGroupsRoute) .add(transactionGroupsPrimaryStatisticsRoute) - .add(transactionLatencyChatsRoute) - .add(transactionThroughputChatsRoute) + .add(transactionLatencyChartsRoute) + .add(transactionThroughputChartsRoute) .add(transactionGroupsComparisonStatisticsRoute) // Service map diff --git a/x-pack/plugins/apm/server/routes/default_api_types.ts b/x-pack/plugins/apm/server/routes/default_api_types.ts index fdc1e8ebe5a55f..990b462a520d23 100644 --- a/x-pack/plugins/apm/server/routes/default_api_types.ts +++ b/x-pack/plugins/apm/server/routes/default_api_types.ts @@ -18,4 +18,6 @@ export const comparisonRangeRt = t.partial({ comparisonEnd: isoToEpochRt, }); +export const environmentRt = t.partial({ environment: t.string }); + export const uiFiltersRt = t.type({ uiFilters: t.string }); diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index cc9db2e6a48551..073a91bfe15487 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -11,7 +11,7 @@ import { getErrorDistribution } from '../lib/errors/distribution/get_distributio import { getErrorGroupSample } from '../lib/errors/get_error_group_sample'; import { getErrorGroups } from '../lib/errors/get_error_groups'; import { setupRequest } from '../lib/helpers/setup_request'; -import { uiFiltersRt, rangeRt } from './default_api_types'; +import { environmentRt, uiFiltersRt, rangeRt } from './default_api_types'; export const errorsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/errors', @@ -24,6 +24,7 @@ export const errorsRoute = createRoute({ sortField: t.string, sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -33,9 +34,10 @@ export const errorsRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { sortField, sortDirection } = params.query; + const { environment, sortField, sortDirection } = params.query; return getErrorGroups({ + environment, serviceName, sortField, sortDirection, @@ -51,13 +53,15 @@ export const errorGroupsRoute = createRoute({ serviceName: t.string, groupId: t.string, }), - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([environmentRt, uiFiltersRt, rangeRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName, groupId } = context.params.path; - return getErrorGroupSample({ serviceName, groupId, setup }); + const { environment } = context.params.query; + + return getErrorGroupSample({ environment, serviceName, groupId, setup }); }, }); @@ -71,6 +75,7 @@ export const errorDistributionRoute = createRoute({ t.partial({ groupId: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -80,7 +85,7 @@ export const errorDistributionRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { groupId } = params.query; - return getErrorDistribution({ serviceName, groupId, setup }); + const { environment, groupId } = params.query; + return getErrorDistribution({ environment, serviceName, groupId, setup }); }, }); diff --git a/x-pack/plugins/apm/server/routes/metrics.ts b/x-pack/plugins/apm/server/routes/metrics.ts index d07504a1046ee5..08376ed0e37ffb 100644 --- a/x-pack/plugins/apm/server/routes/metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { setupRequest } from '../lib/helpers/setup_request'; import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_data_by_agent'; import { createRoute } from './create_route'; -import { uiFiltersRt, rangeRt } from './default_api_types'; +import { environmentRt, uiFiltersRt, rangeRt } from './default_api_types'; export const metricsChartsRoute = createRoute({ endpoint: `GET /api/apm/services/{serviceName}/metrics/charts`, @@ -24,6 +24,7 @@ export const metricsChartsRoute = createRoute({ t.partial({ serviceNodeName: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -33,8 +34,9 @@ export const metricsChartsRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { agentName, serviceNodeName } = params.query; + const { agentName, environment, serviceNodeName } = params.query; return await getMetricsChartDataByAgent({ + environment, setup, serviceName, agentName, diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 7cca6cd0a19430..65c7b245958f32 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -12,7 +12,7 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; import { notifyFeatureUsage } from '../feature'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { isActivePlatinumLicense } from '../../common/license_check'; @@ -22,9 +22,9 @@ export const serviceMapRoute = createRoute({ params: t.type({ query: t.intersection([ t.partial({ - environment: t.string, serviceName: t.string, }), + environmentRt, rangeRt, ]), }), diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index ff064e0571d138..24c7c6e3e23d7e 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -25,23 +25,33 @@ import { getServiceTransactionTypes } from '../lib/services/get_service_transact import { getThroughput } from '../lib/services/get_throughput'; import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; import { createRoute } from './create_route'; -import { comparisonRangeRt, rangeRt, uiFiltersRt } from './default_api_types'; +import { + comparisonRangeRt, + environmentRt, + rangeRt, + uiFiltersRt, +} from './default_api_types'; import { withApmSpan } from '../utils/with_apm_span'; +import { + latencyAggregationTypeRt, + LatencyAggregationType, +} from '../../common/latency_aggregation_types'; export const servicesRoute = createRoute({ endpoint: 'GET /api/apm/services', params: t.type({ - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([environmentRt, uiFiltersRt, rangeRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - + const { environment } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); const services = await getServices({ + environment, setup, searchAggregatedTransactions, logger: context.logger, @@ -273,6 +283,7 @@ export const serviceErrorGroupsRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -296,6 +307,7 @@ export const serviceErrorGroupsRoute = createRoute({ const { path: { serviceName }, query: { + environment, numBuckets, pageIndex, size, @@ -304,7 +316,9 @@ export const serviceErrorGroupsRoute = createRoute({ transactionType, }, } = context.params; + return getServiceErrorGroups({ + environment, serviceName, setup, size, @@ -325,6 +339,7 @@ export const serviceThroughputRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string }), + environmentRt, uiFiltersRt, rangeRt, comparisonRangeRt, @@ -335,6 +350,7 @@ export const serviceThroughputRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { + environment, transactionType, comparisonStart, comparisonEnd, @@ -355,12 +371,14 @@ export const serviceThroughputRoute = createRoute({ const [currentPeriod, previousPeriod] = await Promise.all([ getThroughput({ ...commonProps, + environment, start, end, }), comparisonStart && comparisonEnd ? getThroughput({ ...commonProps, + environment, start: comparisonStart, end: comparisonEnd, }).then((coordinates) => @@ -387,7 +405,12 @@ export const serviceInstancesRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ - t.type({ transactionType: t.string, numBuckets: toNumberRt }), + t.type({ + latencyAggregationType: latencyAggregationTypeRt, + transactionType: t.string, + numBuckets: toNumberRt, + }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -396,13 +419,17 @@ export const serviceInstancesRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType, numBuckets } = context.params.query; + const { environment, transactionType, numBuckets } = context.params.query; + const latencyAggregationType = (context.params.query + .latencyAggregationType as unknown) as LatencyAggregationType; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return getServiceInstances({ + environment, + latencyAggregationType, serviceName, setup, transactionType, @@ -420,9 +447,9 @@ export const serviceDependenciesRoute = createRoute({ }), query: t.intersection([ t.type({ - environment: t.string, numBuckets: toNumberRt, }), + environmentRt, rangeRt, ]), }), diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 722675906487ce..5d3f99be7af344 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -10,23 +10,25 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getTrace } from '../lib/traces/get_trace'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; export const tracesRoute = createRoute({ endpoint: 'GET /api/apm/traces', params: t.type({ - query: t.intersection([rangeRt, uiFiltersRt]), + query: t.intersection([environmentRt, rangeRt, uiFiltersRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); + const { environment } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); + return getTransactionGroupList( - { type: 'top_traces', searchAggregatedTransactions }, + { environment, type: 'top_traces', searchAggregatedTransactions }, setup ); }, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index bef96cb7f0767e..5a4be216a817c2 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Boom from '@hapi/boom'; import * as t from 'io-ts'; import { LatencyAggregationType, @@ -25,7 +24,7 @@ import { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; /** * Returns a list of transactions grouped by name @@ -39,6 +38,7 @@ export const transactionGroupsRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -47,7 +47,7 @@ export const transactionGroupsRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType } = context.params.query; + const { environment, transactionType } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup @@ -55,6 +55,7 @@ export const transactionGroupsRoute = createRoute({ return getTransactionGroupList( { + environment, type: 'top_transactions', serviceName, transactionType, @@ -71,6 +72,7 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({ params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -91,10 +93,11 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({ const { path: { serviceName }, - query: { latencyAggregationType, transactionType }, + query: { environment, latencyAggregationType, transactionType }, } = context.params; return getServiceTransactionGroups({ + environment, setup, serviceName, searchAggregatedTransactions, @@ -110,6 +113,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -133,6 +137,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ const { path: { serviceName }, query: { + environment, transactionNames, latencyAggregationType, numBuckets, @@ -141,6 +146,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ } = context.params; return getServiceTransactionGroupComparisonStatistics({ + environment, setup, serviceName, transactionNames, @@ -152,7 +158,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ }, }); -export const transactionLatencyChatsRoute = createRoute({ +export const transactionLatencyChartsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency', params: t.type({ path: t.type({ @@ -166,6 +172,7 @@ export const transactionLatencyChatsRoute = createRoute({ transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -176,22 +183,18 @@ export const transactionLatencyChatsRoute = createRoute({ const logger = context.logger; const { serviceName } = context.params.path; const { + environment, transactionType, transactionName, latencyAggregationType, } = context.params.query; - if (!setup.uiFilters.environment) { - throw Boom.badRequest( - `environment is a required property of the ?uiFilters JSON for transaction_groups/charts.` - ); - } - const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); const options = { + environment, serviceName, transactionType, transactionName, @@ -222,7 +225,7 @@ export const transactionLatencyChatsRoute = createRoute({ }, }); -export const transactionThroughputChatsRoute = createRoute({ +export const transactionThroughputChartsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/throughput', params: t.type({ @@ -234,25 +237,25 @@ export const transactionThroughputChatsRoute = createRoute({ t.partial({ transactionName: t.string }), uiFiltersRt, rangeRt, + environmentRt, ]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType, transactionName } = context.params.query; - - if (!setup.uiFilters.environment) { - throw Boom.badRequest( - `environment is a required property of the ?uiFilters JSON for transaction_groups/charts.` - ); - } + const { + environment, + transactionType, + transactionName, + } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return await getThroughputCharts({ + environment, serviceName, transactionType, transactionName, @@ -278,6 +281,7 @@ export const transactionChartsDistributionRoute = createRoute({ transactionId: t.string, traceId: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -287,6 +291,7 @@ export const transactionChartsDistributionRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { + environment, transactionType, transactionName, transactionId = '', @@ -298,6 +303,7 @@ export const transactionChartsDistributionRoute = createRoute({ ); return getTransactionDistribution({ + environment, serviceName, transactionType, transactionName, @@ -318,6 +324,7 @@ export const transactionChartsBreakdownRoute = createRoute({ query: t.intersection([ t.type({ transactionType: t.string }), t.partial({ transactionName: t.string }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -326,9 +333,14 @@ export const transactionChartsBreakdownRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionName, transactionType } = context.params.query; + const { + environment, + transactionName, + transactionType, + } = context.params.query; return getTransactionBreakdown({ + environment, serviceName, transactionName, transactionType, @@ -345,6 +357,7 @@ export const transactionChartsErrorRateRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ + environmentRt, uiFiltersRt, rangeRt, t.type({ transactionType: t.string }), @@ -356,13 +369,14 @@ export const transactionChartsErrorRateRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { transactionType, transactionName } = params.query; + const { environment, transactionType, transactionName } = params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return getErrorRate({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 19ac562121d9dc..4df638cc2c5df2 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -88,7 +88,7 @@ export async function inspectSearchParams( }, } ) as APMConfig, - uiFilters: { environment: 'test' }, + uiFilters: {}, esFilter: [{ term: { 'service.environment': 'test' } }], indices: { /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/x-pack/plugins/beats_management/server/routes/tokens/create.ts b/x-pack/plugins/beats_management/server/routes/tokens/create.ts index d61e96900e04b8..c44f9c2dd4e7dc 100644 --- a/x-pack/plugins/beats_management/server/routes/tokens/create.ts +++ b/x-pack/plugins/beats_management/server/routes/tokens/create.ts @@ -50,11 +50,7 @@ export const registerCreateTokenRoute = (router: BeatsManagementRouter) => { }); } catch (err) { beatsManagement.framework.log(err.message); - return response.internalError({ - body: { - message: 'An error occurred, please check your Kibana logs', - }, - }); + throw new Error('An error occurred, please check your Kibana logs'); } } ) diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index 88ea8700bd464a..afd3d1408e1f1a 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -1752,6 +1752,12 @@ export const ComponentStrings = { description: 'This column in the table contains the date/time the workpad was last updated.', }), + getTableActionsColumnTitle: () => + i18n.translate('xpack.canvas.workpadLoader.table.actionsColumnTitle', { + defaultMessage: 'Actions', + description: + 'This column in the table contains the actions that can be taken on a workpad.', + }), }, WorkpadManager: { getModalTitle: () => diff --git a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx index 521ced0d731f2c..3156b14f209f1a 100644 --- a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx +++ b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx @@ -49,6 +49,7 @@ export const ConfirmModal: FunctionComponent = (props) => { cancelButtonText={cancelButtonText} defaultFocusedButton="confirm" buttonColor="danger" + data-test-subj="canvasConfirmModal" > {message} diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 5a33b25399f77e..25c17fabe9fad8 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -213,7 +213,7 @@ export class WorkpadLoader extends React.PureComponent { width: '20%', render: (date) => this.props.formatDate(date), }, - { name: '', actions, width: '5%' }, + { name: strings.getTableActionsColumnTitle(), actions, width: '100px' }, ]; const sorting = { @@ -310,6 +310,7 @@ export class WorkpadLoader extends React.PureComponent { onClick={this.openRemoveConfirm} disabled={!canUserWrite} aria-label={strings.getDeleteButtonAriaLabel(selectedWorkpads.length)} + data-test-subj="deleteWorkpadButton" > {strings.getDeleteButtonLabel(selectedWorkpads.length)} @@ -331,6 +332,7 @@ export class WorkpadLoader extends React.PureComponent { display="default" compressed className="canvasWorkpad__upload--compressed" + aria-label={strings.getFilePickerPlaceholder()} initialPromptText={strings.getFilePickerPlaceholder()} onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)} accept="application/json" diff --git a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js b/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js index 3f128a1758c3f9..8055be32ac481a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js +++ b/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js @@ -37,6 +37,7 @@ export const WorkpadManager = ({ onClose }) => { { id: 'workpadTemplates', name: strings.getWorkpadTemplatesTabLabel(), + 'data-test-subj': 'workpadTemplates', content: ( @@ -50,7 +51,9 @@ export const WorkpadManager = ({ onClose }) => { - {strings.getModalTitle()} + +

{strings.getModalTitle()}

+
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot index 489827246e998a..e984aae3356366 100644 --- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot @@ -95,6 +95,7 @@ exports[`Storyshots components/WorkpadTemplates default 1`] = ` />
{rows.length > 0 && ( diff --git a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts index 2abdda1932535a..b1fe4bc798f6bc 100644 --- a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts +++ b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts @@ -20,7 +20,7 @@ export const catchErrorHandler: ( statusCode: error.output.statusCode, }); } - return response.internalError({ body: error }); + throw error; } }; }; diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index 618625cd6cdd43..1e95ee809e7679 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -147,8 +147,8 @@ describe('Retrieve ES Fields', () => { callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); - const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); - - expect(response.status).toBe(500); + await expect( + routeHandler(mockRouteContext, request, kibanaResponseFactory) + ).rejects.toThrowError('Index not found'); }); }); diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index 49643ca1f4d0c7..33a93952b0e2d7 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/case/common/api/cases/case.ts @@ -123,6 +123,7 @@ export const CaseResponseRt = rt.intersection([ version: rt.string, }), rt.partial({ + subCaseIds: rt.array(rt.string), subCases: rt.array(SubCaseResponseRt), comments: rt.array(CommentResponseRt), }), diff --git a/x-pack/plugins/case/common/api/cases/comment.ts b/x-pack/plugins/case/common/api/cases/comment.ts index cfc6099fa4bb57..41ad0e87f14d2f 100644 --- a/x-pack/plugins/case/common/api/cases/comment.ts +++ b/x-pack/plugins/case/common/api/cases/comment.ts @@ -52,7 +52,11 @@ export const ContextTypeUserRt = rt.type({ export const AlertCommentRequestRt = rt.type({ type: rt.union([rt.literal(CommentType.generatedAlert), rt.literal(CommentType.alert)]), alertId: rt.union([rt.array(rt.string), rt.string]), - index: rt.string, + index: rt.union([rt.array(rt.string), rt.string]), + rule: rt.type({ + id: rt.union([rt.string, rt.null]), + name: rt.union([rt.string, rt.null]), + }), }); const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]); @@ -108,6 +112,7 @@ export const CommentsResponseRt = rt.type({ export const AllCommentsResponseRt = rt.array(CommentResponseRt); +export type AttributesTypeAlerts = rt.TypeOf; export type CommentAttributes = rt.TypeOf; export type CommentRequest = rt.TypeOf; export type CommentResponse = rt.TypeOf; diff --git a/x-pack/plugins/case/common/api/cases/user_actions.ts b/x-pack/plugins/case/common/api/cases/user_actions.ts index de9e88993df9a2..6c8e0de80903df 100644 --- a/x-pack/plugins/case/common/api/cases/user_actions.ts +++ b/x-pack/plugins/case/common/api/cases/user_actions.ts @@ -49,6 +49,7 @@ const CaseUserActionResponseRT = rt.intersection([ case_id: rt.string, comment_id: rt.union([rt.string, rt.null]), }), + rt.partial({ sub_case_id: rt.string }), ]); export const CaseUserActionAttributesRt = CaseUserActionBasicRT; diff --git a/x-pack/plugins/case/common/api/helpers.ts b/x-pack/plugins/case/common/api/helpers.ts index 9c290c0a4d6128..00c8ff402c802e 100644 --- a/x-pack/plugins/case/common/api/helpers.ts +++ b/x-pack/plugins/case/common/api/helpers.ts @@ -13,6 +13,7 @@ import { SUB_CASE_DETAILS_URL, SUB_CASES_URL, CASE_PUSH_URL, + SUB_CASE_USER_ACTIONS_URL, } from '../constants'; export const getCaseDetailsUrl = (id: string): string => { @@ -38,6 +39,11 @@ export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): str export const getCaseUserActionUrl = (id: string): string => { return CASE_USER_ACTIONS_URL.replace('{case_id}', id); }; + +export const getSubCaseUserActionUrl = (caseID: string, subCaseID: string): string => { + return SUB_CASE_USER_ACTIONS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseID); +}; + export const getCasePushUrl = (caseId: string, connectorId: string): string => { return CASE_PUSH_URL.replace('{case_id}', caseId).replace('{connector_id}', connectorId); }; diff --git a/x-pack/plugins/case/common/constants.ts b/x-pack/plugins/case/common/constants.ts index 5d34ed120ff6f8..cc69c7ecc29094 100644 --- a/x-pack/plugins/case/common/constants.ts +++ b/x-pack/plugins/case/common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { DEFAULT_MAX_SIGNALS } from '../../security_solution/common/constants'; + export const APP_ID = 'case'; /** @@ -19,6 +21,7 @@ export const CASE_CONFIGURE_CONNECTORS_URL = `${CASE_CONFIGURE_URL}/connectors`; export const SUB_CASES_PATCH_DEL_URL = `${CASES_URL}/sub_cases`; export const SUB_CASES_URL = `${CASE_DETAILS_URL}/sub_cases`; export const SUB_CASE_DETAILS_URL = `${CASE_DETAILS_URL}/sub_cases/{sub_case_id}`; +export const SUB_CASE_USER_ACTIONS_URL = `${SUB_CASE_DETAILS_URL}/user_actions`; export const CASE_COMMENTS_URL = `${CASE_DETAILS_URL}/comments`; export const CASE_COMMENT_DETAILS_URL = `${CASE_DETAILS_URL}/comments/{comment_id}`; @@ -45,3 +48,10 @@ export const SUPPORTED_CONNECTORS = [ JIRA_ACTION_TYPE_ID, RESILIENT_ACTION_TYPE_ID, ]; + +/** + * Alerts + */ + +export const MAX_ALERTS_PER_SUB_CASE = 5000; +export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS; diff --git a/x-pack/plugins/case/server/client/cases/create.test.ts b/x-pack/plugins/case/server/client/cases/create.test.ts index 065825472954b8..3016a57f218757 100644 --- a/x-pack/plugins/case/server/client/cases/create.test.ts +++ b/x-pack/plugins/case/server/client/cases/create.test.ts @@ -76,6 +76,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -174,6 +175,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -239,6 +241,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/client/cases/get.ts b/x-pack/plugins/case/server/client/cases/get.ts index eab43a0c4d4536..ab0b97abbcb76a 100644 --- a/x-pack/plugins/case/server/client/cases/get.ts +++ b/x-pack/plugins/case/server/client/cases/get.ts @@ -26,19 +26,24 @@ export const get = async ({ includeComments = false, includeSubCaseComments = false, }: GetParams): Promise => { - const theCase = await caseService.getCase({ - client: savedObjectsClient, - id, - }); + const [theCase, subCasesForCaseId] = await Promise.all([ + caseService.getCase({ + client: savedObjectsClient, + id, + }), + caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }), + ]); + + const subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id); if (!includeComments) { return CaseResponseRt.encode( flattenCaseSavedObject({ savedObject: theCase, + subCaseIds, }) ); } - const theComments = await caseService.getAllCaseComments({ client: savedObjectsClient, id, @@ -53,6 +58,7 @@ export const get = async ({ flattenCaseSavedObject({ savedObject: theCase, comments: theComments.saved_objects, + subCaseIds, totalComment: theComments.total, totalAlerts: countAlertsForID({ comments: theComments, id }), }) diff --git a/x-pack/plugins/case/server/client/cases/mock.ts b/x-pack/plugins/case/server/client/cases/mock.ts index 2be9f410598312..809c4ad1ea1bd7 100644 --- a/x-pack/plugins/case/server/client/cases/mock.ts +++ b/x-pack/plugins/case/server/client/cases/mock.ts @@ -54,6 +54,10 @@ export const commentAlert: CommentResponse = { id: 'mock-comment-1', alertId: 'alert-id-1', index: 'alert-index-1', + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, type: CommentType.alert as const, created_at: '2019-11-25T21:55:00.177Z', created_by: { diff --git a/x-pack/plugins/case/server/client/cases/update.test.ts b/x-pack/plugins/case/server/client/cases/update.test.ts index 53e233c74deb4e..7a3e4458f25c54 100644 --- a/x-pack/plugins/case/server/client/cases/update.test.ts +++ b/x-pack/plugins/case/server/client/cases/update.test.ts @@ -71,6 +71,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -166,6 +167,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -233,6 +235,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "in-progress", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -300,6 +303,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -371,6 +375,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", diff --git a/x-pack/plugins/case/server/client/cases/utils.ts b/x-pack/plugins/case/server/client/cases/utils.ts index 78bdc6d282c698..fda4142bf77c7b 100644 --- a/x-pack/plugins/case/server/client/cases/utils.ts +++ b/x-pack/plugins/case/server/client/cases/utils.ts @@ -314,6 +314,7 @@ export const getCommentContextFromAttributes = ( type: attributes.type, alertId: attributes.alertId, index: attributes.index, + rule: attributes.rule, }; default: return { diff --git a/x-pack/plugins/case/server/client/comments/add.test.ts b/x-pack/plugins/case/server/client/comments/add.test.ts index 315203a1f5e1d9..c9b1e4fd132722 100644 --- a/x-pack/plugins/case/server/client/comments/add.test.ts +++ b/x-pack/plugins/case/server/client/comments/add.test.ts @@ -75,6 +75,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -94,6 +98,10 @@ describe('addComment', () => { "index": "test-index", "pushed_at": null, "pushed_by": null, + "rule": Object { + "id": "test-rule1", + "name": "test-rule", + }, "type": "alert", "updated_at": null, "updated_by": null, @@ -231,6 +239,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -265,6 +277,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -406,6 +422,10 @@ describe('addComment', () => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }) .catch((e) => { @@ -478,6 +498,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }) .catch((e) => { diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts index 7dd1b4a8f6c5cd..0a86c1825fedce 100644 --- a/x-pack/plugins/case/server/client/comments/add.ts +++ b/x-pack/plugins/case/server/client/comments/add.ts @@ -38,6 +38,8 @@ import { import { CaseServiceSetup, CaseUserActionServiceSetup } from '../../services'; import { CommentableCase } from '../../common'; import { CaseClientHandler } from '..'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; +import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants'; async function getSubCase({ caseService, @@ -56,7 +58,20 @@ async function getSubCase({ }): Promise> { const mostRecentSubCase = await caseService.getMostRecentSubCase(savedObjectsClient, caseId); if (mostRecentSubCase && mostRecentSubCase.attributes.status !== CaseStatuses.closed) { - return mostRecentSubCase; + const subCaseAlertsAttachement = await caseService.getAllSubCaseComments({ + client: savedObjectsClient, + id: mostRecentSubCase.id, + options: { + fields: [], + filter: `${CASE_COMMENT_SAVED_OBJECT}.attributes.type: ${CommentType.generatedAlert}`, + page: 1, + perPage: 1, + }, + }); + + if (subCaseAlertsAttachement.total <= MAX_GENERATED_ALERTS_PER_SUB_CASE) { + return mostRecentSubCase; + } } const newSubCase = await caseService.createSubCase({ @@ -160,7 +175,11 @@ const addGeneratedAlerts = async ({ await caseClient.updateAlertsStatus({ ids, status: subCase.attributes.status, - indices: new Set([newComment.attributes.index]), + indices: new Set([ + ...(Array.isArray(newComment.attributes.index) + ? newComment.attributes.index + : [newComment.attributes.index]), + ]), }); } @@ -282,7 +301,11 @@ export const addComment = async ({ await caseClient.updateAlertsStatus({ ids, status: updatedCase.status, - indices: new Set([newComment.attributes.index]), + indices: new Set([ + ...(Array.isArray(newComment.attributes.index) + ? newComment.attributes.index + : [newComment.attributes.index]), + ]), }); } diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts index a8f64227daf83f..ba5677426c2228 100644 --- a/x-pack/plugins/case/server/client/types.ts +++ b/x-pack/plugins/case/server/client/types.ts @@ -59,6 +59,7 @@ export interface CaseClientGetAlerts { export interface CaseClientGetUserActions { caseId: string; + subCaseId?: string; } export interface MappingsClient { diff --git a/x-pack/plugins/case/server/client/user_actions/get.ts b/x-pack/plugins/case/server/client/user_actions/get.ts index 8a4e45f71b9ca3..f6371b8e8b1e7b 100644 --- a/x-pack/plugins/case/server/client/user_actions/get.ts +++ b/x-pack/plugins/case/server/client/user_actions/get.ts @@ -6,7 +6,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { CASE_SAVED_OBJECT, CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; +import { + CASE_SAVED_OBJECT, + CASE_COMMENT_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../saved_object_types'; import { CaseUserActionsResponseRt, CaseUserActionsResponse } from '../../../common/api'; import { CaseUserActionServiceSetup } from '../../services'; @@ -14,24 +18,36 @@ interface GetParams { savedObjectsClient: SavedObjectsClientContract; userActionService: CaseUserActionServiceSetup; caseId: string; + subCaseId?: string; } export const get = async ({ savedObjectsClient, userActionService, caseId, + subCaseId, }: GetParams): Promise => { const userActions = await userActionService.getUserActions({ client: savedObjectsClient, caseId, + subCaseId, }); return CaseUserActionsResponseRt.encode( - userActions.saved_objects.map((ua) => ({ - ...ua.attributes, - action_id: ua.id, - case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '', - comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, - })) + userActions.saved_objects.reduce((acc, ua) => { + if (subCaseId == null && ua.references.some((uar) => uar.type === SUB_CASE_SAVED_OBJECT)) { + return acc; + } + return [ + ...acc, + { + ...ua.attributes, + action_id: ua.id, + case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '', + comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, + sub_case_id: ua.references.find((r) => r.type === SUB_CASE_SAVED_OBJECT)?.id ?? '', + }, + ]; + }, []) ); }; diff --git a/x-pack/plugins/case/server/common/utils.test.ts b/x-pack/plugins/case/server/common/utils.test.ts index d89feb009f8069..5e6a86358de256 100644 --- a/x-pack/plugins/case/server/common/utils.test.ts +++ b/x-pack/plugins/case/server/common/utils.test.ts @@ -99,6 +99,10 @@ describe('common utils', () => { alertId: ['a', 'b', 'c'], index: '', type: CommentType.generatedAlert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -118,6 +122,10 @@ describe('common utils', () => { alertId: ['a', 'b', 'c'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -139,6 +147,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, { comment: '', @@ -164,6 +176,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -197,6 +213,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -224,6 +244,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, diff --git a/x-pack/plugins/case/server/connectors/case/index.test.ts b/x-pack/plugins/case/server/connectors/case/index.test.ts index 6b7e395bae4dc1..4be519858db18c 100644 --- a/x-pack/plugins/case/server/connectors/case/index.test.ts +++ b/x-pack/plugins/case/server/connectors/case/index.test.ts @@ -717,6 +717,10 @@ describe('case connector', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, }, }, }; diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index 34b407616cfe48..a64cba567ce46d 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -122,23 +122,48 @@ async function executor( /** * This converts a connector style generated alert ({_id: string} | {_id: string}[]) to the expected format of addComment. */ +interface AttachmentAlerts { + ids: string[]; + indices: string[]; + rule: { id: string | null; name: string | null }; +} export const transformConnectorComment = (comment: CommentSchemaType): CommentRequest => { if (isCommentGeneratedAlert(comment)) { - const alertId: string[] = []; - if (Array.isArray(comment.alerts)) { - alertId.push( - ...comment.alerts.map((alert: { _id: string }) => { - return alert._id; - }) + try { + const genAlerts: Array<{ + _id: string; + _index: string; + ruleId: string | undefined; + ruleName: string | undefined; + }> = JSON.parse( + `${comment.alerts.substring(0, comment.alerts.lastIndexOf('__SEPARATOR__'))}]`.replace( + /__SEPARATOR__/gi, + ',' + ) + ); + + const { ids, indices, rule } = genAlerts.reduce( + (acc, { _id, _index, ruleId, ruleName }) => { + // Mutation is faster than destructing. + // Mutation usually leads to side effects but for this scenario it's ok to do it. + acc.ids.push(_id); + acc.indices.push(_index); + // We assume one rule per batch of alerts + acc.rule = { id: ruleId ?? null, name: ruleName ?? null }; + return acc; + }, + { ids: [], indices: [], rule: { id: null, name: null } } ); - } else { - alertId.push(comment.alerts._id); + + return { + type: CommentType.generatedAlert, + alertId: ids, + index: indices, + rule, + }; + } catch (e) { + throw new Error(`Error parsing generated alert in case connector -> ${e.message}`); } - return { - type: CommentType.generatedAlert, - alertId, - index: comment.index, - }; } else { return comment; } diff --git a/x-pack/plugins/case/server/connectors/case/schema.ts b/x-pack/plugins/case/server/connectors/case/schema.ts index cdeb00209f846b..ac34ad40cfa13a 100644 --- a/x-pack/plugins/case/server/connectors/case/schema.ts +++ b/x-pack/plugins/case/server/connectors/case/schema.ts @@ -17,17 +17,9 @@ const ContextTypeUserSchema = schema.object({ comment: schema.string(), }); -const AlertIDSchema = schema.object( - { - _id: schema.string(), - }, - { unknowns: 'ignore' } -); - const ContextTypeAlertGroupSchema = schema.object({ type: schema.literal(CommentType.generatedAlert), - alerts: schema.oneOf([schema.arrayOf(AlertIDSchema), AlertIDSchema]), - index: schema.string(), + alerts: schema.string(), }); export type ContextTypeGeneratedAlertType = typeof ContextTypeAlertGroupSchema.type; @@ -37,6 +29,10 @@ const ContextTypeAlertSchema = schema.object({ // allowing either an array or a single value to preserve the previous API of attaching a single alert ID alertId: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), index: schema.string(), + rule: schema.object({ + id: schema.nullable(schema.string()), + name: schema.nullable(schema.string()), + }), }); export type ContextTypeAlertSchemaType = typeof ContextTypeAlertSchema.type; diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/case/server/connectors/index.ts index 056ccff2733a76..898d61301a1402 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -65,3 +65,27 @@ export const isCommentAlert = ( ): comment is ContextTypeAlertSchemaType => { return comment.type === CommentType.alert; }; + +/** + * Separator field for the case connector alerts string parser. + */ +const separator = '__SEPARATOR__'; + +interface AlertIDIndex { + _id: string; + _index: string; + ruleId: string; + ruleName: string; +} + +/** + * Creates the format that the connector's parser is expecting, it should result in something like this: + * [{"_id":"1","_index":"index1"}__SEPARATOR__{"_id":"id2","_index":"index2"}__SEPARATOR__] + * + * This should only be used for testing purposes. + */ +export function createAlertsString(alerts: AlertIDIndex[]) { + return `[${alerts.reduce((acc, alert) => { + return `${acc}${JSON.stringify(alert)}${separator}`; + }, '')}]`; +} diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 2fe0be3e08ede6..e67a6f6dd3344b 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -346,6 +346,10 @@ export const mockCaseComments: Array> = [ }, pushed_at: null, pushed_by: null, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, updated_at: '2019-11-25T22:32:30.608Z', updated_by: { full_name: 'elastic', @@ -379,6 +383,10 @@ export const mockCaseComments: Array> = [ }, pushed_at: null, pushed_by: null, + rule: { + id: 'rule-id-2', + name: 'rule-name-2', + }, updated_at: '2019-11-25T22:32:30.608Z', updated_by: { full_name: 'elastic', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts index 9dec910f9fc46f..1ebd336c83af75 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts @@ -69,6 +69,10 @@ describe('PATCH comment', () => { type: CommentType.alert, alertId: 'new-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule', + }, id: commentID, version: 'WzYsMV0=', }, @@ -218,6 +222,10 @@ describe('PATCH comment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule', + }, id: 'mock-comment-1', version: 'WzEsMV0=', }, diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts index fb51b8f76d0ef4..807ec0d089a524 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts @@ -68,6 +68,10 @@ describe('POST comment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule-name', + }, }, }); diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts index e50d14e5c66c4f..b3f87211c95475 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts @@ -81,6 +81,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -154,6 +155,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -222,6 +224,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "in-progress", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index 53829157c5b049..e1669203d3dedb 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -213,6 +213,7 @@ describe('POST cases', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts index 06e929cc40e6bb..488f32a795811f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -9,9 +9,9 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; +import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; -export function initGetAllUserActionsApi({ router }: RouteDeps) { +export function initGetAllCaseUserActionsApi({ router }: RouteDeps) { router.get( { path: CASE_USER_ACTIONS_URL, @@ -39,3 +39,34 @@ export function initGetAllUserActionsApi({ router }: RouteDeps) { } ); } + +export function initGetAllSubCaseUserActionsApi({ router }: RouteDeps) { + router.get( + { + path: SUB_CASE_USER_ACTIONS_URL, + validate: { + params: schema.object({ + case_id: schema.string(), + sub_case_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + if (!context.case) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); + } + + const caseClient = context.case.getCaseClient(); + const caseId = request.params.case_id; + const subCaseId = request.params.sub_case_id; + + try { + return response.ok({ + body: await caseClient.getUserActions({ caseId, subCaseId }), + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts index f2fd986dd8a3ae..12d1da36077c79 100644 --- a/x-pack/plugins/case/server/routes/api/index.ts +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -14,7 +14,10 @@ import { initPushCaseApi } from './cases/push_case'; import { initGetReportersApi } from './cases/reporters/get_reporters'; import { initGetCasesStatusApi } from './cases/status/get_status'; import { initGetTagsApi } from './cases/tags/get_tags'; -import { initGetAllUserActionsApi } from './cases/user_actions/get_all_user_actions'; +import { + initGetAllCaseUserActionsApi, + initGetAllSubCaseUserActionsApi, +} from './cases/user_actions/get_all_user_actions'; import { initDeleteCommentApi } from './cases/comments/delete_comment'; import { initDeleteAllCommentsApi } from './cases/comments/delete_all_comments'; @@ -52,7 +55,8 @@ export function initCaseApi(deps: RouteDeps) { initPatchCasesApi(deps); initPostCaseApi(deps); initPushCaseApi(deps); - initGetAllUserActionsApi(deps); + initGetAllCaseUserActionsApi(deps); + initGetAllSubCaseUserActionsApi(deps); // Sub cases initGetSubCaseApi(deps); initPatchSubCasesApi(deps); diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts index 1efec927efb62f..f6bc1e4f718971 100644 --- a/x-pack/plugins/case/server/routes/api/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -401,6 +401,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -440,6 +441,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "Data Destruction", @@ -483,6 +485,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -530,6 +533,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -594,6 +598,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -649,6 +654,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -727,6 +733,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -781,6 +788,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index bc82f656f477b3..084b1a17a1434a 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { badRequest, boomify, isBoom } from '@hapi/boom'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; @@ -120,7 +121,8 @@ export interface AlertInfo { const accumulateIndicesAndIDs = (comment: CommentAttributes, acc: AlertInfo): AlertInfo => { if (isCommentRequestTypeAlertOrGenAlert(comment)) { acc.ids.push(...getAlertIds(comment)); - acc.indices.add(comment.index); + const indices = Array.isArray(comment.index) ? comment.index : [comment.index]; + indices.forEach((index) => acc.indices.add(index)); } return acc; }; @@ -249,12 +251,14 @@ export const flattenCaseSavedObject = ({ totalComment = comments.length, totalAlerts = 0, subCases, + subCaseIds, }: { savedObject: SavedObject; comments?: Array>; totalComment?: number; totalAlerts?: number; subCases?: SubCaseResponse[]; + subCaseIds?: string[]; }): CaseResponse => ({ id: savedObject.id, version: savedObject.version ?? '0', @@ -264,6 +268,7 @@ export const flattenCaseSavedObject = ({ ...savedObject.attributes, connector: transformESConnectorToCaseConnector(savedObject.attributes.connector), subCases, + subCaseIds: !isEmpty(subCaseIds) ? subCaseIds : undefined, }); export const flattenSubCaseSavedObject = ({ diff --git a/x-pack/plugins/case/server/saved_object_types/comments.ts b/x-pack/plugins/case/server/saved_object_types/comments.ts index 9eabf744f2e132..a4fdc24b6e4eec 100644 --- a/x-pack/plugins/case/server/saved_object_types/comments.ts +++ b/x-pack/plugins/case/server/saved_object_types/comments.ts @@ -63,6 +63,16 @@ export const caseCommentSavedObjectType: SavedObjectsType = { }, }, }, + rule: { + properties: { + id: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + }, + }, updated_at: { type: 'date', }, diff --git a/x-pack/plugins/case/server/saved_object_types/migrations.ts b/x-pack/plugins/case/server/saved_object_types/migrations.ts index a0b22c49d0bc6d..21ef27de1ec851 100644 --- a/x-pack/plugins/case/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/case/server/saved_object_types/migrations.ts @@ -173,8 +173,9 @@ interface SanitizedComment { type: CommentType; } -interface SanitizedCommentAssociationType { +interface SanitizedCommentFoSubCases { associationType: AssociationType; + rule: { id: string | null; name: string | null }; } export const commentsMigrations = { @@ -192,11 +193,12 @@ export const commentsMigrations = { }, '7.12.0': ( doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { + ): SavedObjectSanitizedDoc => { return { ...doc, attributes: { ...doc.attributes, + rule: { id: null, name: null }, associationType: AssociationType.case, }, references: doc.references || [], diff --git a/x-pack/plugins/case/server/scripts/sub_cases/index.ts b/x-pack/plugins/case/server/scripts/sub_cases/index.ts index 2ea9718d18487e..9dd577c40c74e6 100644 --- a/x-pack/plugins/case/server/scripts/sub_cases/index.ts +++ b/x-pack/plugins/case/server/scripts/sub_cases/index.ts @@ -16,6 +16,7 @@ import { import { CommentType } from '../../../common/api/cases/comment'; import { CASES_URL } from '../../../common/constants'; import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common'; +import { ContextTypeGeneratedAlertType, createAlertsString } from '../../connectors'; main(); @@ -105,6 +106,18 @@ async function handleGenGroupAlerts(argv: any) { } console.log('Case id: ', caseID); + const comment: ContextTypeGeneratedAlertType = { + type: CommentType.generatedAlert, + alerts: createAlertsString( + argv.ids.map((id: string) => ({ + _id: id, + _index: argv.signalsIndex, + ruleId: argv.ruleID, + ruleName: argv.ruleName, + })) + ), + }; + const executeResp = await client.request< ActionTypeExecutorResult >({ @@ -115,11 +128,7 @@ async function handleGenGroupAlerts(argv: any) { subAction: 'addComment', subActionParams: { caseId: caseID, - comment: { - type: CommentType.generatedAlert, - alerts: argv.ids.map((id: string) => ({ _id: id })), - index: argv.signalsIndex, - }, + comment, }, }, }, @@ -175,6 +184,18 @@ async function main() { type: 'string', default: '.siem-signals-default', }, + ruleID: { + alias: 'ri', + describe: 'siem signals rule id', + type: 'string', + default: 'rule-id', + }, + ruleName: { + alias: 'rn', + describe: 'siem signals rule name', + type: 'string', + default: 'rule-name', + }, }) .demandOption(['ids']); }, diff --git a/x-pack/plugins/case/server/services/alerts/index.ts b/x-pack/plugins/case/server/services/alerts/index.ts index 320d32ac0d7887..a19e533418bc9a 100644 --- a/x-pack/plugins/case/server/services/alerts/index.ts +++ b/x-pack/plugins/case/server/services/alerts/index.ts @@ -11,6 +11,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient } from 'kibana/server'; import { CaseStatuses } from '../../../common/api'; +import { MAX_ALERTS_PER_SUB_CASE } from '../../../common/constants'; export type AlertServiceContract = PublicMethodsOf; @@ -95,14 +96,14 @@ export class AlertService { query: { bool: { filter: { - bool: { - should: ids.map((_id) => ({ match: { _id } })), - minimum_should_match: 1, + ids: { + values: ids, }, }, }, }, }, + size: MAX_ALERTS_PER_SUB_CASE, ignore_unavailable: true, }); diff --git a/x-pack/plugins/case/server/services/user_actions/index.ts b/x-pack/plugins/case/server/services/user_actions/index.ts index 091775827c6a61..d05ada0dba30cd 100644 --- a/x-pack/plugins/case/server/services/user_actions/index.ts +++ b/x-pack/plugins/case/server/services/user_actions/index.ts @@ -13,11 +13,16 @@ import { } from 'kibana/server'; import { CaseUserActionAttributes } from '../../../common/api'; -import { CASE_USER_ACTION_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../saved_object_types'; +import { + CASE_USER_ACTION_SAVED_OBJECT, + CASE_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../saved_object_types'; import { ClientArgs } from '..'; interface GetCaseUserActionArgs extends ClientArgs { caseId: string; + subCaseId?: string; } export interface UserActionItem { @@ -41,18 +46,20 @@ export interface CaseUserActionServiceSetup { export class CaseUserActionService { constructor(private readonly log: Logger) {} public setup = async (): Promise => ({ - getUserActions: async ({ client, caseId }: GetCaseUserActionArgs) => { + getUserActions: async ({ client, caseId, subCaseId }: GetCaseUserActionArgs) => { try { + const id = subCaseId ?? caseId; + const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT; const caseUserActionInfo = await client.find({ type: CASE_USER_ACTION_SAVED_OBJECT, fields: [], - hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + hasReference: { type, id }, page: 1, perPage: 1, }); return await client.find({ type: CASE_USER_ACTION_SAVED_OBJECT, - hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + hasReference: { type, id }, page: 1, perPage: caseUserActionInfo.total, sortField: 'action_at', diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts index 10cb83877f57f3..608e369828de6f 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts @@ -55,7 +55,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } } @@ -71,7 +71,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts index bc18d5c4b10f81..868e847bd6bf48 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts @@ -41,7 +41,7 @@ export const registerDeleteRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts index 0e6ebc5270986f..632fdb03dd5887 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts @@ -37,7 +37,7 @@ export const registerFetchRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts index cd1cb227a3eba7..3529fe313dbb16 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts @@ -48,7 +48,7 @@ export const registerGetRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts index 3bdcd4824a9d45..a9aa94cdf4f299 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts @@ -40,7 +40,7 @@ export const registerPauseRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts index 594ef79eaedc96..1c6396d0b35022 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts @@ -40,7 +40,7 @@ export const registerResumeRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts index e4d0b7d9d65149..a3e7c3544ca370 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts @@ -54,7 +54,7 @@ export const registerUpdateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts index ac956a45b87026..130adb2e0b9892 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts @@ -64,7 +64,7 @@ export const registerPermissionsRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts index f37dafca86c985..6636f6b1c5accc 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts @@ -36,7 +36,7 @@ export const registerStatsRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts index b254606af8a867..f44e5a749baad7 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts @@ -59,7 +59,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts index ffb560ebd5f2bf..c72706cf5d10dc 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts @@ -56,7 +56,7 @@ export const registerFetchRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts index 36feb2e69a3f5c..cdcfff97e645db 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts @@ -72,7 +72,7 @@ export const registerGetRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts index dd76114d30215a..2e4e71278df0e9 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts @@ -38,7 +38,7 @@ export const registerPauseRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts index 22206c70fdbbf8..34f204f3b64b96 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts @@ -38,7 +38,7 @@ export const registerResumeRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts index 05bf99e2d8c67f..848408e14662fd 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts @@ -39,7 +39,7 @@ export const registerUnfollowRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts index 0b61db42cc7635..933d13a0a223c0 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts @@ -87,7 +87,7 @@ export const registerUpdateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx new file mode 100644 index 00000000000000..fd567f52ada249 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx @@ -0,0 +1,26 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiToken } from '@elastic/eui'; + +import { BoostIcon } from './boost_icon'; +import { BoostType } from './types'; + +describe('BoostIcon', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders a token according to the provided type', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiToken).prop('iconType')).toBe('tokenNumber'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx new file mode 100644 index 00000000000000..2570a29274d069 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx @@ -0,0 +1,28 @@ +/* + * 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 React from 'react'; + +import { EuiToken } from '@elastic/eui'; + +import { BOOST_TYPE_TO_ICON_MAP } from './constants'; +import { BoostType } from './types'; + +interface Props { + type: BoostType; +} + +export const BoostIcon: React.FC = ({ type }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts index 211995b2a7d188..9fdbb8e979b316 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts @@ -7,33 +7,66 @@ import { i18n } from '@kbn/i18n'; +import { BoostType } from './types'; + +export const FIELD_FILTER_CUTOFF = 10; + export const RELEVANCE_TUNING_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title', { defaultMessage: 'Relevance Tuning' } ); export const UPDATE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.updateSuccess', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess', { defaultMessage: 'Relevance successfully tuned. The changes will impact your results shortly.', } ); export const DELETE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.deleteSuccess', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess', { defaultMessage: 'Relevance has been reset to default values. The change will impact your results shortly.', } ); export const RESET_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.resetConfirmation', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation', { defaultMessage: 'Are you sure you want to restore relevance defaults?', } ); export const DELETE_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.deleteConfirmation', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation', { defaultMessage: 'Are you sure you want to delete this boost?', } ); +export const PROXIMITY_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel', + { + defaultMessage: 'Proximity', + } +); +export const FUNCTIONAL_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel', + { + defaultMessage: 'Functional', + } +); +export const VALUE_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel', + { + defaultMessage: 'Value', + } +); +export const BOOST_TYPE_TO_DISPLAY_MAP = { + [BoostType.Proximity]: PROXIMITY_DISPLAY, + [BoostType.Functional]: FUNCTIONAL_DISPLAY, + [BoostType.Value]: VALUE_DISPLAY, +}; + +export const BOOST_TYPE_TO_ICON_MAP = { + [BoostType.Value]: 'tokenNumber', + [BoostType.Functional]: 'tokenFunction', + [BoostType.Proximity]: 'tokenGeo', +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx index ea8d43d183ef0f..85cf3dd8a68c97 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx @@ -4,20 +4,34 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockActions } from '../../../__mocks__/kea.mock'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { RelevanceTuning } from './relevance_tuning'; +import { RelevanceTuningForm } from './relevance_tuning_form'; describe('RelevanceTuning', () => { + let wrapper: ShallowWrapper; + + const actions = { + initializeRelevanceTuning: jest.fn(), + }; + beforeEach(() => { jest.clearAllMocks(); + setMockActions(actions); + wrapper = shallow(); }); it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); + expect(wrapper.find(RelevanceTuningForm).exists()).toBe(true); + }); + + it('initializes relevance tuning data', () => { + expect(actions.initializeRelevanceTuning).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx index 83e83c0f9ea43e..f65a86b1e02f0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx @@ -5,26 +5,41 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useActions } from 'kea'; import { EuiPageHeader, EuiPageHeaderSection, EuiTitle, - EuiPageContentBody, - EuiPageContent, + EuiText, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { RELEVANCE_TUNING_TITLE } from './constants'; +import { RelevanceTuningForm } from './relevance_tuning_form'; +import { RelevanceTuningLogic } from './relevance_tuning_logic'; interface Props { engineBreadcrumb: string[]; } export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { + const { initializeRelevanceTuning } = useActions(RelevanceTuningLogic); + + useEffect(() => { + initializeRelevanceTuning(); + }, []); + return ( <> @@ -33,13 +48,26 @@ export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => {

{RELEVANCE_TUNING_TITLE}

+ + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description', + { + defaultMessage: 'Set field weights and boosts', + } + )} + + - - - - - + + + + + + + + ); }; diff --git a/x-pack/plugins/apm/common/utils/range_filter.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts similarity index 60% rename from x-pack/plugins/apm/common/utils/range_filter.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts index 8d5b7d5e1beb1e..89e344860b2eb8 100644 --- a/x-pack/plugins/apm/common/utils/range_filter.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts @@ -5,12 +5,4 @@ * 2.0. */ -export function rangeFilter(start: number, end: number) { - return { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }; -} +export { RelevanceTuningForm } from './relevance_tuning_form'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss new file mode 100644 index 00000000000000..749fca6f798110 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss @@ -0,0 +1,20 @@ +.relevanceTuningForm { + &__item { + width: 100%; + margin-left: $euiSizeS; + } + + &__panel + &__panel { + margin-top: $euiSizeM; + } + + &__panel .euiAccordion__button { + &:hover, + &:focus { + text-decoration: none; + h3 { + text-decoration: underline; + } + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx new file mode 100644 index 00000000000000..3965e9e81d1ba3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, mount, ReactWrapper, ShallowWrapper } from 'enzyme'; + +import { EuiFieldSearch } from '@elastic/eui'; + +import { BoostType } from '../types'; + +import { RelevanceTuningForm } from './relevance_tuning_form'; +import { RelevanceTuningItem } from './relevance_tuning_item'; + +describe('RelevanceTuningForm', () => { + const values = { + filterInputValue: '', + schemaFields: ['foo', 'bar', 'baz'], + filteredSchemaFields: ['foo', 'bar'], + schema: { + foo: 'text', + bar: 'number', + }, + searchSettings: { + boosts: { + foo: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + }, + search_fields: { + bar: { + weight: 1, + }, + }, + }, + }; + const actions = { + setFilterValue: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + describe('fields', () => { + let wrapper: ReactWrapper; + let relevantTuningItems: any; + + beforeAll(() => { + setMockValues(values); + + wrapper = mount(); + relevantTuningItems = wrapper.find(RelevanceTuningItem); + }); + + it('renders a list of fields that may or may not have been filterd by user input', () => { + // The length is 2 because we're only pulling values from `filteredSchemaFields`, which + // is the list of schema fields that has been filtered by user input down to 2 + expect(relevantTuningItems.length).toBe(2); + }); + + it('will pass the schema field name in the "name" prop of each list item', () => { + expect(relevantTuningItems.at(0).prop('name')).toBe('foo'); + expect(relevantTuningItems.at(1).prop('name')).toBe('bar'); + }); + + it('will pass the schema type of the field in the "type" prop of each list item', () => { + expect(relevantTuningItems.at(0).prop('type')).toBe('text'); + expect(relevantTuningItems.at(1).prop('type')).toBe('number'); + }); + + it('will pass a list of boosts in the "boosts" field of each list item, or undefined if none exist', () => { + expect(relevantTuningItems.at(0).prop('boosts')).toEqual([ + { + factor: 2, + type: BoostType.Value, + }, + ]); + expect(relevantTuningItems.at(1).prop('boosts')).toBeUndefined(); + }); + + it('will pass the search_field configuration for the field in the "field" prop of each list item, or undefined if none exists', () => { + expect(relevantTuningItems.at(0).prop('field')).toBeUndefined(); + expect(relevantTuningItems.at(1).prop('field')).toEqual({ + weight: 1, + }); + }); + }); + + describe('field filtering', () => { + let searchField: ShallowWrapper; + + beforeEach(() => { + setMockValues({ + ...values, + filterInputValue: 'test', + schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'], + }); + const wrapper = shallow(); + searchField = wrapper.find(EuiFieldSearch); + }); + + it('renders an input box for filtering the field list in case there is a large quantity of fields', () => { + expect(searchField.exists()).toBe(true); + }); + + it('initializes the input box with the user input value stored in state', () => { + expect(searchField.prop('value')).toBe('test'); + }); + + it('updates the user input value stored in state whenever the input box value changes', () => { + searchField.simulate('change', { + target: { + value: 'new value', + }, + }); + + expect(actions.setFilterValue).toHaveBeenCalledWith('new value'); + }); + + it('will not render a field filter if there are 10 or less fields', () => { + setMockValues({ + ...values, + schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + }); + const wrapper = shallow(); + expect(wrapper.find(EuiFieldSearch).exists()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx new file mode 100644 index 00000000000000..e39c93fd5de3cb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx @@ -0,0 +1,106 @@ +/* + * 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 React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, + EuiFieldSearch, + EuiSpacer, + EuiAccordion, + EuiPanel, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FIELD_FILTER_CUTOFF } from '../constants'; +import { RelevanceTuningLogic } from '../relevance_tuning_logic'; + +import { RelevanceTuningItem } from './relevance_tuning_item'; +import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; + +import './relevance_tuning_form.scss'; + +export const RelevanceTuningForm: React.FC = () => { + const { + filterInputValue, + schemaFields, + filteredSchemaFields, + schema, + searchSettings, + } = useValues(RelevanceTuningLogic); + const { setFilterValue } = useActions(RelevanceTuningLogic); + + return ( +
+
+ {/* TODO SchemaConflictCallout */} + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title', + { + defaultMessage: 'Manage fields', + } + )} +

+
+
+
+ {schemaFields.length > FIELD_FILTER_CUTOFF && ( + setFilterValue(e.target.value)} + placeholder={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder', + { + defaultMessage: 'Filter {schemaFieldsLength} fields...', + values: { + schemaFieldsLength: schemaFields.length, + }, + } + )} + fullWidth + /> + )} + + {filteredSchemaFields.map((fieldName) => ( + + + } + paddingSize="s" + > + + + + ))} + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx new file mode 100644 index 00000000000000..6043e7ae65b268 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { SchemaTypes } from '../../../../shared/types'; + +import { BoostIcon } from '../boost_icon'; +import { Boost, BoostType, SearchField } from '../types'; + +import { RelevanceTuningItem } from './relevance_tuning_item'; +import { ValueBadge } from './value_badge'; + +describe('RelevanceTuningItem', () => { + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + boosts: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + field: { + weight: 1, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('boosts prop', () => { + const renderComponentWithBoostsConfig = (boosts?: Boost[]) => { + return shallow( + + ); + }; + + describe('when there are boosts for this field', () => { + it('renders an icon for each boost that is applied', () => { + const wrapper = renderComponentWithBoostsConfig([ + { + factor: 2, + type: BoostType.Value, + }, + { + factor: 3, + type: BoostType.Proximity, + }, + ]); + expect(wrapper.find(BoostIcon).length).toBe(2); + expect(wrapper.find(BoostIcon).map((euiToken) => euiToken.prop('type'))).toEqual([ + BoostType.Value, + BoostType.Proximity, + ]); + }); + }); + + describe('when there are no boosts for this field', () => { + const wrapper = renderComponentWithBoostsConfig(); + + it('renders an icon for each boost that is applied', () => { + expect(wrapper.find(BoostIcon).length).toBe(0); + }); + }); + }); + + describe('field prop', () => { + const renderComponentWithFieldConfig = (field?: SearchField) => { + return shallow( + + ); + }; + + describe('when weight is set to any positive number', () => { + const wrapper = renderComponentWithFieldConfig({ + weight: 1, + }); + + it('will show the weight with an "enabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('1'); + expect(valueBadge.prop('disabled')).toBe(false); + }); + }); + + describe('when weight set to "0", which means this field will not be searched', () => { + const wrapper = renderComponentWithFieldConfig({ + weight: 0, + }); + + it('will show 0 with a "disabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('0'); + expect(valueBadge.prop('disabled')).toBe(true); + }); + }); + + describe('when there is no weight set, which means this field will not be searched', () => { + const wrapper = renderComponentWithFieldConfig(); + + it('will show "0" with a "disabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('0'); + expect(valueBadge.prop('disabled')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx new file mode 100644 index 00000000000000..38cec4825cfe7f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx @@ -0,0 +1,60 @@ +/* + * 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 React from 'react'; + +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiTextColor, EuiIcon } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../shared/types'; + +import { BoostIcon } from '../boost_icon'; +import { Boost, SearchField } from '../types'; + +import { ValueBadge } from './value_badge'; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; + field?: SearchField; +} + +export const RelevanceTuningItem: React.FC = ({ name, type, boosts = [], field }) => { + return ( + + + +

{name}

+
+ + {type} + +
+ + + {boosts.map((boost, index) => ( + + + + ))} + + + + {!!field ? field.weight : 0} + + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx new file mode 100644 index 00000000000000..e5a76bc586b80c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx @@ -0,0 +1,53 @@ +/* + * 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 React, { useMemo } from 'react'; + +import { EuiFlexItem, EuiAccordion, EuiFlexGroup, EuiHideFor } from '@elastic/eui'; + +import { BoostIcon } from '../../../boost_icon'; +import { BOOST_TYPE_TO_DISPLAY_MAP } from '../../../constants'; +import { Boost } from '../../../types'; +import { ValueBadge } from '../../value_badge'; + +import { getBoostSummary } from './get_boost_summary'; + +interface Props { + boost: Boost; + id: string; +} + +export const BoostItem: React.FC = ({ id, boost }) => { + const summary = useMemo(() => getBoostSummary(boost), [boost]); + + return ( + + + + + + + {BOOST_TYPE_TO_DISPLAY_MAP[boost.type]} + + {summary} + + + + + {boost.factor} + + + } + paddingSize="s" + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss new file mode 100644 index 00000000000000..53b3c233301b08 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss @@ -0,0 +1,28 @@ +.boosts { + &__select { + min-width: $euiSizeXXL * 4; + } + + &__itemContent { + width: 100%; + } + + &__item { + margin-top: $euiSize; + + & + & { + margin-top: $euiSizeS; + } + } +} + +.boostSelectOption { + .euiContextMenuItem__text { + display: flex; + align-items: center; + + .euiToken { + margin-right: $euiSizeS; + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx new file mode 100644 index 00000000000000..b313e16c0bda11 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 { setMockActions } from '../../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiSuperSelect } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../../shared/types'; + +import { Boosts } from './boosts'; + +describe('Boosts', () => { + const actions = { + addBoost: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const props = { + name: 'foo', + type: 'number' as SchemaTypes, + }; + + it('renders a select box that allows users to create boosts of various types', () => { + const wrapper = shallow(); + + const select = wrapper.find(EuiSuperSelect); + expect(select.prop('options').map((o: any) => o.value)).toEqual([ + 'add-boost', + 'functional', + 'proximity', + 'value', + ]); + }); + + it('will not render functional or proximity options if "type" prop is "text"', () => { + const wrapper = shallow( + + ); + + const select = wrapper.find(EuiSuperSelect); + expect(select.prop('options').map((o: any) => o.value)).toEqual(['add-boost', 'value']); + }); + + it('will add a boost of the selected type when a selection is made', () => { + const wrapper = shallow(); + + wrapper.find(EuiSuperSelect).simulate('change', 'functional'); + + expect(actions.addBoost).toHaveBeenCalledWith('foo', 'functional'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx new file mode 100644 index 00000000000000..1ad27346d2630e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx @@ -0,0 +1,118 @@ +/* + * 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 React, { useMemo } from 'react'; + +import { useActions } from 'kea'; + +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle, EuiSuperSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { TEXT } from '../../../../../../shared/constants/field_types'; +import { SchemaTypes } from '../../../../../../shared/types'; + +import { BoostIcon } from '../../../boost_icon'; +import { FUNCTIONAL_DISPLAY, PROXIMITY_DISPLAY, VALUE_DISPLAY } from '../../../constants'; +import { RelevanceTuningLogic } from '../../../relevance_tuning_logic'; +import { Boost, BoostType } from '../../../types'; + +import { BoostItem } from './boost_item'; + +import './boosts.scss'; + +const BASE_OPTIONS = [ + { + value: 'add-boost', + inputDisplay: i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel', + { + defaultMessage: 'Add boost', + } + ), + disabled: true, + }, + { + value: BoostType.Functional, + inputDisplay: ( + <> + + {FUNCTIONAL_DISPLAY} + + ), + }, + { + value: BoostType.Proximity, + inputDisplay: ( + <> + + {PROXIMITY_DISPLAY} + + ), + }, + { + value: BoostType.Value, + inputDisplay: ( + <> + + {VALUE_DISPLAY} + + ), + }, +]; + +const filterInvalidOptions = (value: BoostType, type: SchemaTypes) => { + // Proximity and Functional boost types are not valid for text fields + if (type === TEXT && [BoostType.Proximity, BoostType.Functional].includes(value)) return false; + return true; +}; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; +} + +export const Boosts: React.FC = ({ name, type, boosts = [] }) => { + const { addBoost } = useActions(RelevanceTuningLogic); + + const selectOptions = useMemo( + () => BASE_OPTIONS.filter((option) => filterInvalidOptions(option.value as BoostType, type)), + [type] + ); + + return ( + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title', + { + defaultMessage: 'Boosts', + } + )} +

+
+
+ + addBoost(name, value as BoostType)} + /> + +
+ {boosts.map((boost, index) => ( + + ))} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts new file mode 100644 index 00000000000000..f6852569213a68 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { Boost, BoostFunction, BoostType, BoostOperation } from '../../../types'; + +import { getBoostSummary } from './get_boost_summary'; + +describe('getBoostSummary', () => { + describe('when the boost type is "value"', () => { + const boost: Boost = { + type: BoostType.Value, + value: ['1', '2'], + factor: 5, + }; + + it('creates a summary that is the joined values', () => { + expect(getBoostSummary(boost)).toEqual('1,2'); + }); + + it('creates an empty summary if there is no value', () => { + expect( + getBoostSummary({ + ...boost, + value: undefined, + }) + ).toEqual(''); + }); + }); + + describe('when the boost type is "proximity"', () => { + const boost: Boost = { + type: BoostType.Proximity, + function: 'gaussian' as BoostFunction, + factor: 5, + }; + + it('creates a summary that is just the name of the function', () => { + expect(getBoostSummary(boost)).toEqual('gaussian'); + }); + + it('creates an empty summary if there is no function', () => { + expect( + getBoostSummary({ + ...boost, + function: undefined, + }) + ).toEqual(''); + }); + }); + + describe('when the boost type is "functional"', () => { + const boost: Boost = { + type: BoostType.Functional, + function: BoostFunction.Gaussian, + operation: BoostOperation.Add, + factor: 5, + }; + + it('creates a summary that is name of the function and operation', () => { + expect(getBoostSummary(boost)).toEqual('gaussian add'); + }); + + it('prints empty if function or operation is missing', () => { + expect(getBoostSummary({ ...boost, function: undefined })).toEqual(BoostOperation.Add); + expect(getBoostSummary({ ...boost, operation: undefined })).toEqual(BoostFunction.Gaussian); + expect(getBoostSummary({ ...boost, function: undefined, operation: undefined })).toEqual(''); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts new file mode 100644 index 00000000000000..f3922ebb0fffe9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts @@ -0,0 +1,18 @@ +/* + * 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 { Boost, BoostType } from '../../../types'; + +export const getBoostSummary = (boost: Boost): string => { + if (boost.type === BoostType.Value) { + return !boost.value ? '' : boost.value.join(','); + } else if (boost.type === BoostType.Proximity) { + return boost.function || ''; + } else { + return [boost.function || '', boost.operation || ''].join(' ').trim(); + } +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts similarity index 86% rename from x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts index e23bae6f2403e6..dc269132769b81 100644 --- a/x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const SET_STATE = 'SET_STATE'; +export { Boosts } from './boosts'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts new file mode 100644 index 00000000000000..dc5b95320d4db6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { RelevanceTuningItemContent } from './relevance_tuning_item_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss new file mode 100644 index 00000000000000..63718a95551fa5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss @@ -0,0 +1,6 @@ +.relevanceTuningForm { + &__itemContent { + border: none; + border-top: $euiBorderThin; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx new file mode 100644 index 00000000000000..18a75766cd67be --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { SchemaTypes } from '../../../../../shared/types'; +import { BoostType } from '../../types'; + +import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; +import { TextSearchToggle } from './text_search_toggle'; +import { WeightSlider } from './weight_slider'; + +describe('RelevanceTuningItemContent', () => { + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + boosts: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + field: { + weight: 1, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + const textSearchToggle = wrapper.find(TextSearchToggle); + expect(textSearchToggle.exists()).toBe(true); + expect(textSearchToggle.prop('name')).toBe(props.name); + expect(textSearchToggle.prop('type')).toBe(props.type); + expect(textSearchToggle.prop('field')).toBe(props.field); + + const weightSlider = wrapper.find(WeightSlider); + expect(weightSlider.exists()).toBe(true); + expect(weightSlider.prop('name')).toBe(props.name); + expect(weightSlider.prop('field')).toBe(props.field); + }); + + it('will not render a WeightSlider if the field prop is empty', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(WeightSlider).exists()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx new file mode 100644 index 00000000000000..29ab559485d776 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx @@ -0,0 +1,39 @@ +/* + * 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 React from 'react'; + +import { EuiPanel } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../shared/types'; + +import { Boost, SearchField } from '../../types'; + +import { Boosts } from './boosts'; +import { TextSearchToggle } from './text_search_toggle'; +import { WeightSlider } from './weight_slider'; + +import './relevance_tuning_item_content.scss'; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; + field?: SearchField; +} + +export const RelevanceTuningItemContent: React.FC = ({ name, type, boosts, field }) => { + return ( + <> + + + {field && } + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx new file mode 100644 index 00000000000000..7225fce5daa611 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx @@ -0,0 +1,122 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiSwitch } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../shared/types'; + +import { TextSearchToggle } from './text_search_toggle'; + +describe('TextSearchToggle', () => { + const actions = { + toggleSearchField: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('typical render', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + field: { + weight: 1, + }, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders a toggle button', () => { + expect(wrapper.find(EuiSwitch).exists()).toBe(true); + }); + + it('shows the toggle button as checked if any value was passed in the "field" prop', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); + }); + + it('shows the toggle as enabled if "text" was passed in the "type" prop', () => { + expect(wrapper.find(EuiSwitch).prop('disabled')).toBe(false); + }); + + it('shows a relevant label if "text" was passed in the "type" prop', () => { + expect(wrapper.find(EuiSwitch).prop('label')).toBe('Search this field'); + }); + + it('will update toggled state when clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', true); + }); + }); + + describe('when a non-"text" type is passed in the "type" prop', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'number' as SchemaTypes, + field: { + weight: 1, + }, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('shows the toggle button as disabled', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); + }); + + it('shows a relevant label', () => { + expect(wrapper.find(EuiSwitch).prop('label')).toBe( + 'Search can only be enabled on text fields' + ); + }); + + it('will not update state when the clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).not.toHaveBeenCalled(); + }); + }); + + describe('when no field prop is passed', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('shows the toggle button as unchecked', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(false); + }); + + it('will update toggled state when clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx new file mode 100644 index 00000000000000..607ddd9c6b0782 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx @@ -0,0 +1,62 @@ +/* + * 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 React from 'react'; + +import { useActions } from 'kea'; + +import { EuiFormRow, EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { TEXT } from '../../../../../shared/constants/field_types'; +import { SchemaTypes } from '../../../../../shared/types'; + +import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; +import { SearchField } from '../../types'; + +interface Props { + name: string; + type: SchemaTypes; + field?: SearchField; +} + +export const TextSearchToggle: React.FC = ({ name, type, field }) => { + const { toggleSearchField } = useActions(RelevanceTuningLogic); + + return ( + + type === TEXT && toggleSearchField(name, !!field)} + checked={!!field} + disabled={type !== TEXT} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx new file mode 100644 index 00000000000000..21a112a4ea9889 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx @@ -0,0 +1,50 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiRange } from '@elastic/eui'; + +import { WeightSlider } from './weight_slider'; + +describe('WeightSlider', () => { + let wrapper: ShallowWrapper; + + const actions = { + updateFieldWeight: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + wrapper = shallow( + + ); + }); + + it('renders with an initial value set', () => { + expect(wrapper.find(EuiRange).exists()).toBe(true); + expect(wrapper.find(EuiRange).prop('value')).toBe(2.2); + }); + + it('updates field weight in state when the value changes', () => { + wrapper.find(EuiRange).simulate('change', { + target: { + value: '1.3', + }, + }); + expect(actions.updateFieldWeight).toHaveBeenCalledWith('foo', 1.3); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx new file mode 100644 index 00000000000000..02e83b81b2cb13 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx @@ -0,0 +1,53 @@ +/* + * 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 React from 'react'; + +import { useActions } from 'kea'; + +import { EuiFormRow, EuiRange } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; +import { SearchField } from '../../types'; + +interface Props { + name: string; + field: SearchField; +} + +export const WeightSlider: React.FC = ({ name, field }) => { + const { updateFieldWeight } = useActions(RelevanceTuningLogic); + + return ( + + + updateFieldWeight( + name, + parseFloat((e as React.ChangeEvent).target.value) + ) + } + showInput + compressed + fullWidth + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss new file mode 100644 index 00000000000000..853edd3fed0a38 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss @@ -0,0 +1,23 @@ +.relevanceTuningForm { + .valueBadge { + display: inline-flex; + align-items: center; + height: $euiSizeL; + border: 1px solid $euiColorLightShade; + border-radius: $euiSizeXS; + // To match the background of EuiToken, for which there is no direct variable to + // reference + background: tintOrShade($euiColorVis1, 90%, 70%); + color: $euiColorVis1; + padding: 0 $euiSizeS; + + .euiIcon { + margin-right: $euiSizeXS; + } + } + + .valueBadge--disabled { + background: transparent; + color: $euiColorMediumShade; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx new file mode 100644 index 00000000000000..8397087ca69b46 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx @@ -0,0 +1,22 @@ +/* + * 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 React from 'react'; + +import classNames from 'classnames'; + +import './value_badge.scss'; + +export const ValueBadge: React.FC<{ children: React.ReactNode; disabled?: boolean }> = ({ + children, + disabled = false, +}) => { + const className = classNames('valueBadge', { + 'valueBadge--disabled': disabled, + }); + return {children}; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 194848bcfc86c0..a7ee6f9755fc4a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -9,7 +9,7 @@ import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../ import { nextTick } from '@kbn/test/jest'; -import { Boost, BoostType } from './types'; +import { Boost, BoostFunction, BoostOperation, BoostType } from './types'; import { RelevanceTuningLogic } from './'; @@ -24,7 +24,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -97,6 +97,18 @@ describe('RelevanceTuningLogic', () => { schemaConflicts, }); }); + + it('should default schemaConflicts if it is not passed', () => { + mount({ + dataLoading: true, + }); + RelevanceTuningLogic.actions.onInitializeRelevanceTuning({ + searchSettings, + schema, + }); + + expect(RelevanceTuningLogic.values.schemaConflicts).toEqual({}); + }); }); describe('setSearchSettings', () => { @@ -237,7 +249,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, boost, ], @@ -265,7 +277,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, value: 5, }, @@ -289,7 +301,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, value: ['5'], }, @@ -324,19 +336,25 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server }, ], }, + search_fields: { + bar: { + weight: 1, + }, + }, }; const searchSettingsWithoutNewBoostProp = { + ...searchSettingsWithNewBoostProp, boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -475,7 +493,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server }, @@ -487,7 +505,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -672,7 +690,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 2, - type: 'value', + type: BoostType.Value, }, ], }, @@ -680,7 +698,7 @@ describe('RelevanceTuningLogic', () => { }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.addBoost('foo', 'functional'); + RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ ...searchSettings, @@ -688,12 +706,12 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 2, - type: 'value', + type: BoostType.Value, }, { factor: 1, newBoost: true, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -709,7 +727,7 @@ describe('RelevanceTuningLogic', () => { }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.addBoost('foo', 'functional'); + RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ ...searchSettings, @@ -718,7 +736,7 @@ describe('RelevanceTuningLogic', () => { { factor: 1, newBoost: true, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -735,11 +753,11 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, { factor: 2, - type: 'value', + type: BoostType.Value, }, ], }, @@ -756,7 +774,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -771,7 +789,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -796,7 +814,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -816,7 +834,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -826,7 +844,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 5, - type: 'functional', + type: BoostType.Functional, }) ); }); @@ -835,7 +853,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -845,7 +863,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 5.3, - type: 'functional', + type: BoostType.Functional, }) ); }); @@ -856,7 +874,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'b', 'c'], }), }); @@ -867,7 +885,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'a', 'c'], }) ); @@ -877,7 +895,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -887,7 +905,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a'], }) ); @@ -902,7 +920,7 @@ describe('RelevanceTuningLogic', () => { }, searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'proximity', + type: BoostType.Proximity, center: 1, }), }); @@ -913,7 +931,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'proximity', + type: BoostType.Proximity, center: 4, }) ); @@ -925,7 +943,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a'], }), }); @@ -936,7 +954,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }) ); @@ -946,7 +964,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -956,7 +974,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['', ''], }) ); @@ -966,7 +984,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }), }); @@ -977,7 +995,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }) ); @@ -989,7 +1007,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'b', 'c'], }), }); @@ -1000,7 +1018,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'c'], }) ); @@ -1010,7 +1028,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -1026,18 +1044,23 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.updateBoostSelectOption('foo', 1, 'function', 'exponential'); + RelevanceTuningLogic.actions.updateBoostSelectOption( + 'foo', + 1, + 'function', + BoostFunction.Exponential + ); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', - function: 'exponential', + type: BoostType.Functional, + function: BoostFunction.Exponential, }) ); }); @@ -1046,18 +1069,23 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.updateBoostSelectOption('foo', 1, 'operation', 'add'); + RelevanceTuningLogic.actions.updateBoostSelectOption( + 'foo', + 1, + 'operation', + BoostOperation.Add + ); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', - operation: 'add', + type: BoostType.Functional, + operation: BoostOperation.Add, }) ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index cd3d8b5686cc05..d567afee9d0627 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -21,7 +21,14 @@ import { DELETE_SUCCESS_MESSAGE, DELETE_CONFIRMATION_MESSAGE, } from './constants'; -import { BaseBoost, Boost, BoostType, SearchSettings } from './types'; +import { + BaseBoost, + Boost, + BoostFunction, + BoostOperation, + BoostType, + SearchSettings, +} from './types'; import { filterIfTerm, parseBoostCenter, @@ -32,7 +39,7 @@ import { interface RelevanceTuningProps { searchSettings: SearchSettings; schema: Schema; - schemaConflicts: SchemaConflicts; + schemaConflicts?: SchemaConflicts; } interface RelevanceTuningActions { @@ -82,7 +89,7 @@ interface RelevanceTuningActions { name: string, boostIndex: number, optionType: keyof BaseBoost, - value: string + value: BoostOperation | BoostFunction ): { name: string; boostIndex: number; @@ -176,7 +183,7 @@ export const RelevanceTuningLogic = kea< schemaConflicts: [ {}, { - onInitializeRelevanceTuning: (_, { schemaConflicts }) => schemaConflicts, + onInitializeRelevanceTuning: (_, { schemaConflicts }) => schemaConflicts || {}, }, ], showSchemaConflictCallout: [ @@ -497,7 +504,11 @@ export const RelevanceTuningLogic = kea< const { searchSettings } = values; const { boosts } = searchSettings; const updatedBoosts = cloneDeep(boosts[name]); - updatedBoosts[boostIndex][optionType] = value; + if (optionType === 'operation') { + updatedBoosts[boostIndex][optionType] = value as BoostOperation; + } else { + updatedBoosts[boostIndex][optionType] = value as BoostFunction; + } actions.setSearchSettings({ ...searchSettings, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index a1ed9797b9f5a5..95bd33aac5b9ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -5,11 +5,26 @@ * 2.0. */ -export type BoostType = 'value' | 'functional' | 'proximity'; +export enum BoostType { + Value = 'value', + Functional = 'functional', + Proximity = 'proximity', +} + +export enum BoostFunction { + Gaussian = 'gaussian', + Exponential = 'exponential', + Linear = 'linear', +} + +export enum BoostOperation { + Add = 'add', + Multiple = 'multiply', +} export interface BaseBoost { - operation?: string; - function?: string; + operation?: BoostOperation; + function?: BoostFunction; } // A boost that comes from the server, before we normalize it has a much looser schema @@ -25,13 +40,13 @@ export interface RawBoost extends BaseBoost { export interface Boost extends RawBoost { value?: string[]; } + +export interface SearchField { + weight: number; +} + export interface SearchSettings { boosts: Record; - search_fields: Record< - string, - { - weight: number; - } - >; + search_fields: Record; result_fields?: object; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts index a6598bf991c13e..1694015ed68615 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts @@ -39,7 +39,7 @@ describe('removeBoostStateProps', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, }, @@ -56,7 +56,7 @@ describe('removeBoostStateProps', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -66,39 +66,46 @@ describe('removeBoostStateProps', () => { }); describe('parseBoostCenter', () => { - it('should parse a boost center', () => { - expect(parseBoostCenter('text', 5)).toEqual(5); - expect(parseBoostCenter('text', '4')).toEqual('4'); + it('should parse the value to a number when the type is number', () => { expect(parseBoostCenter('number', 5)).toEqual(5); expect(parseBoostCenter('number', '5')).toEqual(5); }); + + it('should not try to parse the value when the type is text', () => { + expect(parseBoostCenter('text', 5)).toEqual(5); + expect(parseBoostCenter('text', '4')).toEqual('4'); + }); + + it('should leave text invalid numbers alone', () => { + expect(parseBoostCenter('number', 'foo')).toEqual('foo'); + }); }); describe('normalizeBoostValues', () => { const boosts = { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: 1, }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: '1', }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: [1], }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: ['1'], }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: [ '1', @@ -115,13 +122,13 @@ describe('normalizeBoostValues', () => { ], bar: [ { - type: 'proximity' as BoostType, + type: BoostType.Proximity, factor: 9.5, }, ], sp_def: [ { - type: 'functional' as BoostType, + type: BoostType.Functional, factor: 5, }, ], @@ -129,19 +136,19 @@ describe('normalizeBoostValues', () => { it('converts all value types to string for consistency', () => { expect(normalizeBoostValues(boosts)).toEqual({ - bar: [{ factor: 9.5, type: 'proximity' }], + bar: [{ factor: 9.5, type: BoostType.Proximity }], foo: [ - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, { factor: 9.5, - type: 'value', + type: BoostType.Value, value: ['1', '1', '2', '2', 'true', '[object Object]', '[object Object]'], }, ], - sp_def: [{ type: 'functional', factor: 5 }], + sp_def: [{ type: BoostType.Functional, factor: 5 }], }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 4fd4d480108def..cdfd07b07de912 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -679,6 +679,10 @@ export const DESCRIPTION_LABEL = i18n.translate( } ); +export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', { + defaultMessage: 'and', +}); + export const UPDATE_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.update.label', { defaultMessage: 'Update', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts new file mode 100644 index 00000000000000..37228cf9e7025d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { toSentenceSerial } from './to_sentence_serial'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts new file mode 100644 index 00000000000000..fb6501d3cb943a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { toSentenceSerial } from './to_sentence_serial'; + +describe('toSentenceSerial', () => { + it('works correctly for 1 word', () => { + expect(toSentenceSerial(['One'])).toEqual('One'); + }); + + it('works correctly for 2 words', () => { + expect(toSentenceSerial(['One', 'Two'])).toEqual('One and Two'); + }); + + it('works correctly for 3+ words', () => { + expect(toSentenceSerial(['One', 'Two', 'Three'])).toEqual('One, Two, and Three'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts new file mode 100644 index 00000000000000..ad6383a76adc22 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts @@ -0,0 +1,15 @@ +/* + * 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 { AND } from '../constants'; + +export const toSentenceSerial = (array: string[]) => + array.length === 1 + ? array[0] + : `${array.slice(0, array.length - 1).join(', ')}${ + array.length === 2 ? '' : ',' + } ${AND} ${array.slice(-1)}`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index 8efa61c1ed524f..3e1290292704e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -413,10 +413,6 @@ export const SHARED_EMPTY_DESCRIPTION = i18n.translate( } ); -export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', { - defaultMessage: 'and', -}); - export const LICENSE_CALLOUT_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx new file mode 100644 index 00000000000000..e6f19ff13b3ccf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx @@ -0,0 +1,111 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCallOut, EuiEmptyPrompt } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; +import { ContentSection } from '../../components/shared/content_section'; +import { SourcesTable } from '../../components/shared/sources_table'; + +import { PrivateSources } from './private_sources'; +import { SourcesView } from './sources_view'; + +describe('PrivateSources', () => { + const mockValues = { + account: { canCreatePersonalSources: false, groups: [] }, + dataLoading: false, + contentSources: [], + privateContentSources: [], + serviceTypes: [], + hasPlatinumLicense: true, + }; + + beforeEach(() => { + setMockActions({ initializeSources: jest.fn() }); + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SourcesView)).toHaveLength(1); + }); + + it('renders Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders only shared sources section when canCreatePersonalSources is false', () => { + setMockValues({ ...mockValues }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection)).toHaveLength(1); + }); + + it('renders both shared and private sources sections when canCreatePersonalSources is true', () => { + setMockValues({ ...mockValues, account: { canCreatePersonalSources: true, groups: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection)).toHaveLength(2); + }); + + it('renders license callout when has private sources with non-Platinum license', () => { + setMockValues({ + ...mockValues, + privateContentSources: ['source1', 'source2'], + hasPlatinumLicense: false, + account: { canCreatePersonalSources: true, groups: [] }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); + + it('renders an action button when user can add private sources', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + serviceTypes: [{ configured: true }], + }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection).first().prop('action')).toBeTruthy(); + }); + + it('renders empty prompts if no sources are available', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(2); + }); + + it('renders SourcesTable if sources are available', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + contentSources: ['1', '2'], + privateContentSources: ['1', '2'], + }); + const wrapper = shallow(); + + expect(wrapper.find(SourcesTable)).toHaveLength(2); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx index 3ff14602b979d6..114df3cf41e399 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx @@ -20,9 +20,9 @@ import noSharedSourcesIcon from '../../assets/share_circle.svg'; import { ContentSection } from '../../components/shared/content_section'; import { SourcesTable } from '../../components/shared/sources_table'; import { ADD_SOURCE_PATH, getSourcesPath } from '../../routes'; +import { toSentenceSerial } from '../../utils'; import { - AND, PRIVATE_LINK_TITLE, PRIVATE_HEADER_TITLE, PRIVATE_HEADER_DESCRIPTION, @@ -122,13 +122,6 @@ export const PrivateSources: React.FC = () => { ); - const groupsSentence = - groups.length === 1 - ? `${groups}` - : `${groups.slice(0, groups.length - 1).join(', ')}${ - groups.length === 2 ? '' : ',' - } ${AND} ${groups.slice(-1)}`; - const sharedSourcesSection = ( { }} + values={{ + groups: groups.length, + groupsSentence: toSentenceSerial(groups), + newline:
, + }} /> ) } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx new file mode 100644 index 00000000000000..488eb4b49853bd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues } from '../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCallOut } from '@elastic/eui'; + +import { ViewContentHeader } from '../../components/shared/view_content_header'; + +import { + PRIVATE_CAN_CREATE_PAGE_TITLE, + PRIVATE_VIEW_ONLY_PAGE_TITLE, + PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION, + PRIVATE_CAN_CREATE_PAGE_DESCRIPTION, +} from './constants'; +import { PrivateSourcesLayout } from './private_sources_layout'; + +describe('PrivateSourcesLayout', () => { + const mockValues = { + account: { canCreatePersonalSources: true }, + }; + + const children =

test

; + + beforeEach(() => { + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow({children}); + + expect(wrapper.find('[data-test-subj="TestChildren"]')).toHaveLength(1); + }); + + it('uses correct title and description when private sources are enabled', () => { + const wrapper = shallow({children}); + + expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_CAN_CREATE_PAGE_TITLE); + expect(wrapper.find(ViewContentHeader).prop('description')).toEqual( + PRIVATE_CAN_CREATE_PAGE_DESCRIPTION + ); + }); + + it('uses correct title and description when private sources are disabled', () => { + setMockValues({ account: { canCreatePersonalSources: false } }); + const wrapper = shallow({children}); + + expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_VIEW_ONLY_PAGE_TITLE); + expect(wrapper.find(ViewContentHeader).prop('description')).toEqual( + PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION + ); + }); + + it('renders callout when in read-only mode', () => { + const wrapper = shallow({children}); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts index 62f68748fcea19..53ddc21cba3994 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts @@ -7,7 +7,7 @@ import { MockRouter, mockLogger, mockDependencies } from '../../__mocks__'; -import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; +import { savedObjectsServiceMock } from 'src/core/server/mocks'; jest.mock('../../collectors/lib/telemetry', () => ({ incrementUICounter: jest.fn(), @@ -84,17 +84,17 @@ describe('Enterprise Search Telemetry API', () => { it('throws an error when incrementing fails', async () => { (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => Promise.reject('Failed'))); - await mockRouter.callRoute({ - body: { - product: 'enterprise_search', - action: 'error', - metric: 'error', - }, - }); + await expect( + mockRouter.callRoute({ + body: { + product: 'enterprise_search', + action: 'error', + metric: 'error', + }, + }) + ).rejects.toEqual('Failed'); expect(incrementUICounter).toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalled(); - expect(mockRouter.response.internalError).toHaveBeenCalled(); }); it('throws an error if the Saved Objects service is unavailable', async () => { @@ -104,16 +104,9 @@ describe('Enterprise Search Telemetry API', () => { getSavedObjectsService: null, log: mockLogger, } as any); - await mockRouter.callRoute({}); + await expect(mockRouter.callRoute({})).rejects.toThrow(); expect(incrementUICounter).not.toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalled(); - expect(mockRouter.response.internalError).toHaveBeenCalled(); - expect(loggingSystemMock.collect(mockLogger).error[0][0]).toEqual( - expect.stringContaining( - 'Enterprise Search UI telemetry error: Error: Could not find Saved Objects service' - ) - ); }); describe('validates', () => { diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts index 90afba414c0447..e15be8fcd0d8b8 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts @@ -20,7 +20,7 @@ const productToTelemetryMap = { workplace_search: WS_TELEMETRY_NAME, }; -export function registerTelemetryRoute({ router, getSavedObjectsService, log }: RouteDependencies) { +export function registerTelemetryRoute({ router, getSavedObjectsService }: RouteDependencies) { router.put( { path: '/api/enterprise_search/stats', @@ -43,23 +43,16 @@ export function registerTelemetryRoute({ router, getSavedObjectsService, log }: async (ctx, request, response) => { const { product, action, metric } = request.body; - try { - if (!getSavedObjectsService) throw new Error('Could not find Saved Objects service'); + if (!getSavedObjectsService) throw new Error('Could not find Saved Objects service'); - return response.ok({ - body: await incrementUICounter({ - id: productToTelemetryMap[product], - savedObjects: getSavedObjectsService(), - uiAction: `ui_${action}`, - metric, - }), - }); - } catch (e) { - log.error( - `Enterprise Search UI telemetry error: ${e instanceof Error ? e.stack : e.toString()}` - ); - return response.internalError({ body: 'Enterprise Search UI telemetry failed' }); - } + return response.ok({ + body: await incrementUICounter({ + id: productToTelemetryMap[product], + savedObjects: getSavedObjectsService(), + uiAction: `ui_${action}`, + metric, + }), + }); } ); } diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index 95be71812d06c8..9a9a267c40f32a 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -76,11 +76,7 @@ export function registerExploreRoute({ } } - return response.internalError({ - body: { - message: error.message, - }, - }); + throw error; } } ) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index f1a15d805faf8a..859b4adce50285 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -479,6 +479,29 @@ describe('', () => { component.update(); }); + test('serialization', async () => { + httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + await act(async () => { + testBed = await setup(); + }); + const { component, actions } = testBed; + component.update(); + await actions.delete.enablePhase(); + await actions.setWaitForSnapshotPolicy('test'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + expect(entirePolicy.phases.delete).toEqual({ + min_age: '365d', + actions: { + delete: {}, + wait_for_snapshot: { + policy: 'test', + }, + }, + }); + }); + test('wait for snapshot policy field should correctly display snapshot policy name', () => { expect(testBed.find('snapshotPolicyCombobox').prop('data-currentvalue')).toEqual([ { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 600a660657863c..65fc82b7ccc685 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -354,7 +354,7 @@ export const schema: FormSchema = { }, delete: { min_age: { - defaultValue: '0', + defaultValue: '365', validations: [ { validator: minAgeValidator, diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts index d4d16933c518ce..a6c0592e035e79 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts @@ -71,7 +71,7 @@ export const registerCreateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts index 28773b6233b552..552aa5a9a2888c 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts @@ -54,7 +54,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou }); } - return res.internalError({ body: error }); + throw error; } }) ); @@ -94,7 +94,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts index b7957737d3aae8..1ed6555eb38067 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts @@ -64,7 +64,7 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend return res.ok({ body: privilegesResult }); } catch (e) { - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts index b0113e8566ae2e..42b53ab6ee25b3 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts @@ -55,7 +55,7 @@ export const registerUpdateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index cbb7add344a60e..2f5da4b1d8957a 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -38,7 +38,7 @@ export function registerClearCacheRoute({ router, license, lib }: RouteDependenc }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts index 5a0d692c743185..1a0babfc3a5b1f 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -38,7 +38,7 @@ export function registerCloseRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts index dfe571f352296f..9a022d4595d1c0 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -38,7 +38,7 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts index 8faba8a5d54cd0..b064f3520004a0 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -38,7 +38,7 @@ export function registerFlushRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index d8a777196e7ef5..1c14f660b98c67 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -45,7 +45,7 @@ export function registerForcemergeRoute({ router, license, lib }: RouteDependenc }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts index 4d18650d928265..b669d78f2ba597 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts @@ -40,7 +40,7 @@ export function registerFreezeRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts index b940253635ad0e..0b253b9fe66c97 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts @@ -27,7 +27,7 @@ export function registerListRoute({ router, license, indexDataEnricher, lib }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts index 322eac45d7bd1f..a35ddfcf4d91b2 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -38,7 +38,7 @@ export function registerOpenRoute({ router, license, lib }: RouteDependencies) { }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index a070208d30d4cd..f69d2d90a5b8fc 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -38,7 +38,7 @@ export function registerRefreshRoute({ router, license, lib }: RouteDependencies }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts index b6b8d741c1202c..04b7d760fc1d62 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -43,7 +43,7 @@ export function registerReloadRoute({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts index 55281951790331..3cda4d6b5f1682 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts @@ -35,7 +35,7 @@ export function registerUnfreezeRoute({ router, license, lib }: RouteDependencie }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts index 5cc0c92969ab09..f0b62bacdee426 100644 --- a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts @@ -46,7 +46,7 @@ export function registerMappingRoute({ router, license, lib }: RouteDependencies }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts index 6d355ced5e9939..7a661a9e9e4f49 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts @@ -48,7 +48,7 @@ export function registerLoadRoute({ router, license, lib }: RouteDependencies) { }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts index 1216a9c74e48c4..4c153d6293a79f 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts @@ -46,7 +46,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts index 45bf114805a787..f8385711b55fe2 100644 --- a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts @@ -47,7 +47,7 @@ export function registerStatsRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts index 81286b69f2ded8..97e3c380e13ecd 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts @@ -62,7 +62,7 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts index 88aa8d3a793501..006532cfd4dbe1 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts @@ -104,7 +104,7 @@ export function registerGetOneRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts index d24837fa01cd50..f4554bd2fb1fa6 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts @@ -40,7 +40,7 @@ export function registerSimulateRoute({ router, license, lib }: RouteDependencie }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts index bf88d63572a823..f0070408768cbf 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts @@ -56,7 +56,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx index 972c6f8b14f8c4..64867c5743d0d4 100644 --- a/x-pack/plugins/infra/public/apps/common_providers.tsx +++ b/x-pack/plugins/infra/public/apps/common_providers.tsx @@ -7,18 +7,18 @@ import { AppMountParameters, CoreStart } from 'kibana/public'; import React, { useMemo } from 'react'; +import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { - useUiSetting$, KibanaContextProvider, + useUiSetting$, } from '../../../../../src/plugins/kibana_react/public'; -import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public'; import { createKibanaContextForPlugin } from '../hooks/use_kibana'; import { InfraClientStartDeps } from '../types'; import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider'; import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt'; import { TriggersActionsProvider } from '../utils/triggers_actions_context'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; export const CommonInfraProviders: React.FC<{ appName: string; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx index 766b8076bc93dc..e1427bc96e7e0b 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx @@ -5,19 +5,18 @@ * 2.0. */ +import { CoreStart } from 'kibana/public'; import React from 'react'; import ReactDOM from 'react-dom'; -import { CoreStart } from 'kibana/public'; - -import { I18nProvider } from '@kbn/i18n/react'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { Query, TimeRange } from '../../../../../../src/plugins/data/public'; import { Embeddable, EmbeddableInput, IContainer, } from '../../../../../../src/plugins/embeddable/public'; +import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; +import { CoreProviders } from '../../apps/common_providers'; +import { InfraClientStartDeps } from '../../types'; import { datemathToEpochMillis } from '../../utils/datemath'; import { LazyLogStreamWrapper } from './lazy_log_stream_wrapper'; @@ -33,7 +32,8 @@ export class LogStreamEmbeddable extends Embeddable { private node?: HTMLElement; constructor( - private services: CoreStart, + private core: CoreStart, + private pluginDeps: InfraClientStartDeps, initialInput: LogStreamEmbeddableInput, parent?: IContainer ) { @@ -73,20 +73,18 @@ export class LogStreamEmbeddable extends Embeddable { } ReactDOM.render( - + - -
- -
-
+
+ +
-
, + , this.node ); } diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts index 609a29e5842fe2..d621cae3e628cb 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts @@ -5,31 +5,32 @@ * 2.0. */ -import { CoreStart } from 'kibana/public'; +import { StartServicesAccessor } from 'kibana/public'; import { EmbeddableFactoryDefinition, IContainer, } from '../../../../../../src/plugins/embeddable/public'; +import { InfraClientStartDeps } from '../../types'; import { LogStreamEmbeddable, - LOG_STREAM_EMBEDDABLE, LogStreamEmbeddableInput, + LOG_STREAM_EMBEDDABLE, } from './log_stream_embeddable'; export class LogStreamEmbeddableFactoryDefinition implements EmbeddableFactoryDefinition { public readonly type = LOG_STREAM_EMBEDDABLE; - constructor(private getCoreServices: () => Promise) {} + constructor(private getStartServices: StartServicesAccessor) {} public async isEditable() { - const { application } = await this.getCoreServices(); + const [{ application }] = await this.getStartServices(); return application.capabilities.logs.save as boolean; } public async create(initialInput: LogStreamEmbeddableInput, parent?: IContainer) { - const services = await this.getCoreServices(); - return new LogStreamEmbeddable(services, initialInput, parent); + const [core, plugins] = await this.getStartServices(); + return new LogStreamEmbeddable(core, plugins, initialInput, parent); } public getDisplayName() { diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index d4bb83e8668ba5..07afbfdb5d4ed6 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -52,11 +52,9 @@ export class Plugin implements InfraClientPluginClass { }); } - const getCoreServices = async () => (await core.getStartServices())[0]; - pluginsSetup.embeddable.registerEmbeddableFactory( LOG_STREAM_EMBEDDABLE, - new LogStreamEmbeddableFactoryDefinition(getCoreServices) + new LogStreamEmbeddableFactoryDefinition(core.getStartServices) ); core.application.register({ diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts index 7147f224d09d57..70d29773f76c5d 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts @@ -33,33 +33,27 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { sourceId, nodeType, currentTime } = pipe( - InventoryMetaRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { configuration } = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - - const awsMetadata = await getCloudMetadata( - framework, - requestContext, - configuration, - nodeType, - currentTime - ); - - return response.ok({ - body: InventoryMetaResponseRT.encode(awsMetadata), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const { sourceId, nodeType, currentTime } = pipe( + InventoryMetaRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { configuration } = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + + const awsMetadata = await getCloudMetadata( + framework, + requestContext, + configuration, + nodeType, + currentTime + ); + + return response.ok({ + body: InventoryMetaResponseRT.encode(awsMetadata), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts b/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts index c49d6034c95c41..463ac77891263c 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts @@ -31,65 +31,59 @@ export const initValidateLogAnalysisIndicesRoute = ({ framework }: InfraBackendL validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - validationIndicesRequestPayloadRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const payload = pipe( + validationIndicesRequestPayloadRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const { fields, indices } = payload.data; - const errors: ValidationIndicesError[] = []; + const { fields, indices } = payload.data; + const errors: ValidationIndicesError[] = []; - // Query each pattern individually, to map correctly the errors - await Promise.all( - indices.map(async (index) => { - const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { - allow_no_indices: true, - fields: fields.map((field) => field.name), - ignore_unavailable: true, + // Query each pattern individually, to map correctly the errors + await Promise.all( + indices.map(async (index) => { + const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { + allow_no_indices: true, + fields: fields.map((field) => field.name), + ignore_unavailable: true, + index, + }); + + if (fieldCaps.indices.length === 0) { + errors.push({ + error: 'INDEX_NOT_FOUND', index, }); + return; + } + + fields.forEach(({ name: fieldName, validTypes }) => { + const fieldMetadata = fieldCaps.fields[fieldName]; - if (fieldCaps.indices.length === 0) { + if (fieldMetadata === undefined) { errors.push({ - error: 'INDEX_NOT_FOUND', + error: 'FIELD_NOT_FOUND', index, + field: fieldName, }); - return; - } - - fields.forEach(({ name: fieldName, validTypes }) => { - const fieldMetadata = fieldCaps.fields[fieldName]; + } else { + const fieldTypes = Object.keys(fieldMetadata); - if (fieldMetadata === undefined) { + if (!fieldTypes.every((fieldType) => validTypes.includes(fieldType))) { errors.push({ - error: 'FIELD_NOT_FOUND', + error: `FIELD_NOT_VALID`, index, field: fieldName, }); - } else { - const fieldTypes = Object.keys(fieldMetadata); - - if (!fieldTypes.every((fieldType) => validTypes.includes(fieldType))) { - errors.push({ - error: `FIELD_NOT_VALID`, - index, - field: fieldName, - }); - } } - }); - }) - ); + } + }); + }) + ); - return response.ok({ - body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts index c72590ca01a5ef..bb7c615358c0e1 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts @@ -33,75 +33,69 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesHighlightsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; + const payload = pipe( + logEntriesHighlightsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - let entriesPerHighlightTerm; + const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; - if ('center' in payload) { - entriesPerHighlightTerm = await Promise.all( - highlightTerms.map((highlightTerm) => - logEntries.getLogEntriesAround(requestContext, sourceId, { - startTimestamp, - endTimestamp, - query: parseFilterQuery(query), - center: payload.center, - size, - highlightTerm, - }) - ) - ); - } else { - let cursor: LogEntriesParams['cursor']; - if ('before' in payload) { - cursor = { before: payload.before }; - } else if ('after' in payload) { - cursor = { after: payload.after }; - } + let entriesPerHighlightTerm; - entriesPerHighlightTerm = await Promise.all( - highlightTerms.map((highlightTerm) => - logEntries.getLogEntries(requestContext, sourceId, { - startTimestamp, - endTimestamp, - query: parseFilterQuery(query), - cursor, - size, - highlightTerm, - }) - ) - ); + if ('center' in payload) { + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map((highlightTerm) => + logEntries.getLogEntriesAround(requestContext, sourceId, { + startTimestamp, + endTimestamp, + query: parseFilterQuery(query), + center: payload.center, + size, + highlightTerm, + }) + ) + ); + } else { + let cursor: LogEntriesParams['cursor']; + if ('before' in payload) { + cursor = { before: payload.before }; + } else if ('after' in payload) { + cursor = { after: payload.after }; } - return response.ok({ - body: logEntriesHighlightsResponseRT.encode({ - data: entriesPerHighlightTerm.map(({ entries }) => { - if (entries.length > 0) { - return { - entries, - topCursor: entries[0].cursor, - bottomCursor: entries[entries.length - 1].cursor, - }; - } else { - return { - entries, - topCursor: null, - bottomCursor: null, - }; - } - }), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map((highlightTerm) => + logEntries.getLogEntries(requestContext, sourceId, { + startTimestamp, + endTimestamp, + query: parseFilterQuery(query), + cursor, + size, + highlightTerm, + }) + ) + ); } + + return response.ok({ + body: logEntriesHighlightsResponseRT.encode({ + data: entriesPerHighlightTerm.map(({ entries }) => { + if (entries.length > 0) { + return { + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + }; + } else { + return { + entries, + topCursor: null, + bottomCursor: null, + }; + } + }), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary.ts b/x-pack/plugins/infra/server/routes/log_entries/summary.ts index 4849b56b1d579d..3ff0ded8a7c244 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary.ts @@ -33,38 +33,32 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesSummaryRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; + const payload = pipe( + logEntriesSummaryRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; - const buckets = await logEntries.getLogSummaryBucketsBetween( - requestContext, - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - parseFilterQuery(query) - ); + const buckets = await logEntries.getLogSummaryBucketsBetween( + requestContext, + sourceId, + startTimestamp, + endTimestamp, + bucketSize, + parseFilterQuery(query) + ); - UsageCollector.countLogs(); + UsageCollector.countLogs(); - return response.ok({ - body: logEntriesSummaryResponseRT.encode({ - data: { - start: startTimestamp, - end: endTimestamp, - buckets, - }, - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: logEntriesSummaryResponseRT.encode({ + data: { + start: startTimestamp, + end: endTimestamp, + buckets, + }, + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts index 62a9d15c4e68bc..ca219cac41e2bf 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts @@ -35,44 +35,31 @@ export const initLogEntriesSummaryHighlightsRoute = ({ validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesSummaryHighlightsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const { - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - query, - highlightTerms, - } = payload; + const payload = pipe( + logEntriesSummaryHighlightsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const { sourceId, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; - const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( - requestContext, - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - highlightTerms, - parseFilterQuery(query) - ); + const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( + requestContext, + sourceId, + startTimestamp, + endTimestamp, + bucketSize, + highlightTerms, + parseFilterQuery(query) + ); - return response.ok({ - body: logEntriesSummaryHighlightsResponseRT.encode({ - data: bucketsPerHighlightTerm.map((buckets) => ({ - start: startTimestamp, - end: endTimestamp, - buckets, - })), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: logEntriesSummaryHighlightsResponseRT.encode({ + data: bucketsPerHighlightTerm.map((buckets) => ({ + start: startTimestamp, + end: endTimestamp, + buckets, + })), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index b2abe1c35a3ff1..cc8888e9bd09d3 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -37,65 +37,57 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { nodeId, nodeType, sourceId, timeRange } = pipe( - InfraMetadataRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const { nodeId, nodeType, sourceId, timeRange } = pipe( + InfraMetadataRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const { configuration } = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - const metricsMetadata = await getMetricMetadata( - framework, - requestContext, - configuration, - nodeId, - nodeType, - timeRange - ); - const metricFeatures = pickFeatureName(metricsMetadata.buckets).map( - nameToFeature('metrics') - ); + const { configuration } = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + const metricsMetadata = await getMetricMetadata( + framework, + requestContext, + configuration, + nodeId, + nodeType, + timeRange + ); + const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(nameToFeature('metrics')); - const info = await getNodeInfo( - framework, - requestContext, - configuration, - nodeId, - nodeType, - timeRange - ); - const cloudInstanceId = get(info, 'cloud.instance.id'); + const info = await getNodeInfo( + framework, + requestContext, + configuration, + nodeId, + nodeType, + timeRange + ); + const cloudInstanceId = get(info, 'cloud.instance.id'); - const cloudMetricsMetadata = cloudInstanceId - ? await getCloudMetricsMetadata( - framework, - requestContext, - configuration, - cloudInstanceId, - timeRange - ) - : { buckets: [] }; - const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( - nameToFeature('metrics') - ); - const id = metricsMetadata.id; - const name = metricsMetadata.name || id; - return response.ok({ - body: InfraMetadataRT.encode({ - id, - name, - features: [...metricFeatures, ...cloudMetricsFeatures], - info, - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const cloudMetricsMetadata = cloudInstanceId + ? await getCloudMetricsMetadata( + framework, + requestContext, + configuration, + cloudInstanceId, + timeRange + ) + : { buckets: [] }; + const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( + nameToFeature('metrics') + ); + const id = metricsMetadata.id; + const name = metricsMetadata.name || id; + return response.ok({ + body: InfraMetadataRT.encode({ + id, + name, + features: [...metricFeatures, ...cloudMetricsFeatures], + info, + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metrics_api/index.ts b/x-pack/plugins/infra/server/routes/metrics_api/index.ts index 7d616f5b9dfeae..5c0569d6e7a94f 100644 --- a/x-pack/plugins/infra/server/routes/metrics_api/index.ts +++ b/x-pack/plugins/infra/server/routes/metrics_api/index.ts @@ -29,23 +29,17 @@ export const initMetricsAPIRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - MetricsAPIRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + MetricsAPIRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const metricsApiResponse = await query(client, options); + const client = createSearchClient(requestContext, framework); + const metricsApiResponse = await query(client, options); - return response.ok({ - body: MetricsAPIResponseRT.encode(metricsApiResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: MetricsAPIResponseRT.encode(metricsApiResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts index b8a48df43bc10f..d61dcfad974947 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts @@ -37,55 +37,49 @@ export const initMetricExplorerRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - metricsExplorerRequestBodyRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + metricsExplorerRequestBodyRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const interval = await findIntervalForMetrics(client, options); + const client = createSearchClient(requestContext, framework); + const interval = await findIntervalForMetrics(client, options); - const optionsWithInterval = options.forceInterval - ? options - : { - ...options, - timerange: { - ...options.timerange, - interval: interval ? `>=${interval}s` : options.timerange.interval, - }, - }; + const optionsWithInterval = options.forceInterval + ? options + : { + ...options, + timerange: { + ...options.timerange, + interval: interval ? `>=${interval}s` : options.timerange.interval, + }, + }; - const metricsApiOptions = convertRequestToMetricsAPIOptions(optionsWithInterval); - const metricsApiResponse = await query(client, metricsApiOptions); - const totalGroupings = await queryTotalGroupings(client, metricsApiOptions); - const hasGroupBy = - Array.isArray(metricsApiOptions.groupBy) && metricsApiOptions.groupBy.length > 0; + const metricsApiOptions = convertRequestToMetricsAPIOptions(optionsWithInterval); + const metricsApiResponse = await query(client, metricsApiOptions); + const totalGroupings = await queryTotalGroupings(client, metricsApiOptions); + const hasGroupBy = + Array.isArray(metricsApiOptions.groupBy) && metricsApiOptions.groupBy.length > 0; - const pageInfo: MetricsExplorerPageInfo = { - total: totalGroupings, - afterKey: null, - }; + const pageInfo: MetricsExplorerPageInfo = { + total: totalGroupings, + afterKey: null, + }; - if (metricsApiResponse.info.afterKey) { - pageInfo.afterKey = metricsApiResponse.info.afterKey; - } + if (metricsApiResponse.info.afterKey) { + pageInfo.afterKey = metricsApiResponse.info.afterKey; + } - // If we have a groupBy but there are ZERO groupings returned then we need to - // return an empty array. Otherwise we transform the series to match the current schema. - const series = - hasGroupBy && totalGroupings === 0 - ? [] - : metricsApiResponse.series.map(transformSeries(hasGroupBy)); + // If we have a groupBy but there are ZERO groupings returned then we need to + // return an empty array. Otherwise we transform the series to match the current schema. + const series = + hasGroupBy && totalGroupings === 0 + ? [] + : metricsApiResponse.series.map(transformSeries(hasGroupBy)); - return response.ok({ - body: metricsExplorerResponseRT.encode({ series, pageInfo }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: metricsExplorerResponseRT.encode({ series, pageInfo }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/node_details/index.ts b/x-pack/plugins/infra/server/routes/node_details/index.ts index d407a9f65f983e..8e305226112bd3 100644 --- a/x-pack/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/plugins/infra/server/routes/node_details/index.ts @@ -34,38 +34,32 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( - NodeDetailsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); + const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( + NodeDetailsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); - UsageCollector.countNode(nodeType); + UsageCollector.countNode(nodeType); - const options: InfraMetricsRequestOptions = { - nodeIds: { - nodeId, - cloudId, - }, - nodeType, - sourceConfiguration: source.configuration, - metrics, - timerange, - }; - return response.ok({ - body: NodeDetailsMetricDataResponseRT.encode({ - metrics: await libs.metrics.getMetrics(requestContext, options, request), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const options: InfraMetricsRequestOptions = { + nodeIds: { + nodeId, + cloudId, + }, + nodeType, + sourceConfiguration: source.configuration, + metrics, + timerange, + }; + return response.ok({ + body: NodeDetailsMetricDataResponseRT.encode({ + metrics: await libs.metrics.getMetrics(requestContext, options, request), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/overview/index.ts b/x-pack/plugins/infra/server/routes/overview/index.ts index 4102fd883e9127..fe988abcc2883d 100644 --- a/x-pack/plugins/infra/server/routes/overview/index.ts +++ b/x-pack/plugins/infra/server/routes/overview/index.ts @@ -36,77 +36,71 @@ export const initOverviewRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const overviewRequest = pipe( - OverviewRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const overviewRequest = pipe( + OverviewRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - overviewRequest.sourceId - ); + const client = createSearchClient(requestContext, framework); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + overviewRequest.sourceId + ); - const inventoryModelFields = findInventoryFields('host', source.configuration.fields); + const inventoryModelFields = findInventoryFields('host', source.configuration.fields); - const params = { - index: source.configuration.metricAlias, - body: { - query: { - range: { - [source.configuration.fields.timestamp]: { - gte: overviewRequest.timerange.from, - lte: overviewRequest.timerange.to, - format: 'epoch_millis', - }, + const params = { + index: source.configuration.metricAlias, + body: { + query: { + range: { + [source.configuration.fields.timestamp]: { + gte: overviewRequest.timerange.from, + lte: overviewRequest.timerange.to, + format: 'epoch_millis', }, }, - aggs: { - hosts: { - cardinality: { - field: inventoryModelFields.id, - }, + }, + aggs: { + hosts: { + cardinality: { + field: inventoryModelFields.id, }, - cpu: { - avg: { - field: 'system.cpu.total.norm.pct', - }, + }, + cpu: { + avg: { + field: 'system.cpu.total.norm.pct', }, - memory: { - avg: { - field: 'system.memory.actual.used.pct', - }, + }, + memory: { + avg: { + field: 'system.memory.actual.used.pct', }, }, }, - }; + }, + }; - const esResponse = await client<{}, OverviewESAggResponse>(params); + const esResponse = await client<{}, OverviewESAggResponse>(params); - return response.ok({ - body: { - stats: { - hosts: { - type: 'number', - value: esResponse.aggregations?.hosts.value ?? 0, - }, - cpu: { - type: 'percent', - value: esResponse.aggregations?.cpu.value ?? 0, - }, - memory: { - type: 'percent', - value: esResponse.aggregations?.memory.value ?? 0, - }, + return response.ok({ + body: { + stats: { + hosts: { + type: 'number', + value: esResponse.aggregations?.hosts.value ?? 0, + }, + cpu: { + type: 'percent', + value: esResponse.aggregations?.cpu.value ?? 0, + }, + memory: { + type: 'percent', + value: esResponse.aggregations?.memory.value ?? 0, }, }, - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + }, + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/process_list/index.ts b/x-pack/plugins/infra/server/routes/process_list/index.ts index ec4ec21fc16749..f1ba7a7be03600 100644 --- a/x-pack/plugins/infra/server/routes/process_list/index.ts +++ b/x-pack/plugins/infra/server/routes/process_list/index.ts @@ -35,23 +35,17 @@ export const initProcessListRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - ProcessListAPIRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + ProcessListAPIRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const processListResponse = await getProcessList(client, options); + const client = createSearchClient(requestContext, framework); + const processListResponse = await getProcessList(client, options); - return response.ok({ - body: ProcessListAPIResponseRT.encode(processListResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: ProcessListAPIResponseRT.encode(processListResponse), + }); } ); @@ -64,23 +58,17 @@ export const initProcessListRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - ProcessListAPIChartRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + ProcessListAPIChartRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const processListResponse = await getProcessListChart(client, options); + const client = createSearchClient(requestContext, framework); + const processListResponse = await getProcessListChart(client, options); - return response.ok({ - body: ProcessListAPIChartResponseRT.encode(processListResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: ProcessListAPIChartResponseRT.encode(processListResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 07402a3f3ab5d9..aaf23085d0d600 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -31,29 +31,23 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const snapshotRequest = pipe( - SnapshotRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const snapshotRequest = pipe( + SnapshotRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - snapshotRequest.sourceId - ); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + snapshotRequest.sourceId + ); - UsageCollector.countNode(snapshotRequest.nodeType); - const client = createSearchClient(requestContext, framework); - const snapshotResponse = await getNodes(client, snapshotRequest, source); + UsageCollector.countNode(snapshotRequest.nodeType); + const client = createSearchClient(requestContext, framework); + const snapshotResponse = await getNodes(client, snapshotRequest, source); - return response.ok({ - body: SnapshotNodeResponseRT.encode(snapshotResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: SnapshotNodeResponseRT.encode(snapshotResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts index 35decbacf2a529..7473907b7410b8 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts @@ -8,7 +8,6 @@ import { uniq } from 'lodash'; import { InfraTimerangeInput } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; -import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; import { getMetricsAggregations, InfraSnapshotRequestOptions } from './get_metrics_aggregations'; import { @@ -19,9 +18,6 @@ import { getDatasetForField } from '../../metrics_explorer/lib/get_dataset_for_f const createInterval = async (client: ESSearchClient, options: InfraSnapshotRequestOptions) => { const { timerange } = options; - if (timerange.forceInterval && timerange.interval) { - return getIntervalInSeconds(timerange.interval); - } const aggregations = getMetricsAggregations(options); const modules = await aggregationsToModules(client, aggregations, options); return Math.max( @@ -44,14 +40,21 @@ export const createTimeRangeWithInterval = async ( options: InfraSnapshotRequestOptions ): Promise => { const { timerange } = options; - const calculatedInterval = await createInterval(client, options); + if (timerange.forceInterval) { + return { + interval: timerange.interval, + from: timerange.from, + to: timerange.to, + }; + } if (timerange.ignoreLookback) { return { - interval: `${calculatedInterval}s`, + interval: 'modules', from: timerange.from, to: timerange.to, }; } + const calculatedInterval = await createInterval(client, options); const lookbackSize = Math.max(timerange.lookbackSize || 5, 5); return { interval: `${calculatedInterval}s`, diff --git a/x-pack/plugins/infra/server/routes/source/index.ts b/x-pack/plugins/infra/server/routes/source/index.ts index 5c3827e56ce79c..5ab3275f9ea9e0 100644 --- a/x-pack/plugins/infra/server/routes/source/index.ts +++ b/x-pack/plugins/infra/server/routes/source/index.ts @@ -44,34 +44,28 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { type, sourceId } = request.params; + const { type, sourceId } = request.params; - const [source, logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ - libs.sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId), - libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), - libs.sourceStatus.hasMetricIndices(requestContext, sourceId), - libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType(type)), - ]); + const [source, logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ + libs.sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId), + libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), + libs.sourceStatus.hasMetricIndices(requestContext, sourceId), + libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType(type)), + ]); - if (!source) { - return response.notFound(); - } + if (!source) { + return response.notFound(); + } - const status: InfraSourceStatus = { - logIndicesExist: logIndexStatus !== 'missing', - metricIndicesExist, - indexFields, - }; + const status: InfraSourceStatus = { + logIndicesExist: logIndexStatus !== 'missing', + metricIndicesExist, + indexFields, + }; - return response.ok({ - body: SourceResponseRuntimeType.encode({ source: { ...source, status } }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: SourceResponseRuntimeType.encode({ source: { ...source, status } }), + }); } ); @@ -169,26 +163,20 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { type, sourceId } = request.params; - - const client = createSearchClient(requestContext, framework); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - const indexPattern = - type === 'metrics' ? source.configuration.metricAlias : source.configuration.logAlias; - const results = await hasData(indexPattern, client); - - return response.ok({ - body: { hasData: results }, - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const { type, sourceId } = request.params; + + const client = createSearchClient(requestContext, framework); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + const indexPattern = + type === 'metrics' ? source.configuration.metricAlias : source.configuration.logAlias; + const results = await hasData(indexPattern, client); + + return response.ok({ + body: { hasData: results }, + }); } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index 363254d63a2c7e..afa36e5abe31a1 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -83,7 +83,7 @@ export const registerCreateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts index b23a42b895af92..635ee015be5162 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -51,7 +51,7 @@ export const registerDocumentsRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index 6237f4b6911bdb..3995448d13fbb9 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -44,7 +44,7 @@ export const registerGetRoutes = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); @@ -78,7 +78,7 @@ export const registerGetRoutes = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 52492c3ee6d27a..527b4d4277bf5f 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -44,28 +44,24 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend }, } = ctx; - try { - const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( - 'transport.request', - { - path: '/_security/user/_has_privileges', - method: 'POST', - body: { - cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, - }, - } - ); - - if (!hasAllPrivileges) { - privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); + const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( + 'transport.request', + { + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, + }, } + ); - privilegesResult.hasAllPrivileges = hasAllPrivileges; - - return res.ok({ body: privilegesResult }); - } catch (e) { - return res.internalError({ body: e }); + if (!hasAllPrivileges) { + privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); } + + privilegesResult.hasAllPrivileges = hasAllPrivileges; + + return res.ok({ body: privilegesResult }); }) ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index efa2a84daca28c..f02aa0a8d5ed6d 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -52,7 +52,7 @@ export const registerSimulateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 30cfe1b2505c2c..8776aace5ad789 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -65,7 +65,7 @@ export const registerUpdateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap index 158c7fa4aeec37..992301af13ad04 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap @@ -8,6 +8,11 @@ exports[`DatatableComponent it renders actions column when there are row actions { + const table: Datatable = { + type: 'datatable', + columns: [ + { + id: 'a', + name: 'a', + meta: { + type: 'number', + }, + }, + ], + rows: [{ a: 123 }], + }; + const CellRenderer = createGridCell( + { + a: { convert: (x) => `formatted ${x}` } as FieldFormat, + }, + DataContext + ); + + it('renders formatted value', () => { + const instance = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(instance.text()).toEqual('formatted 123'); + }); + + it('set class with text alignment', () => { + const cell = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(cell.find('.lnsTableCell').prop('className')).toContain('--right'); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index a6e1e3386bcf35..2261dd06b532ba 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -14,19 +14,21 @@ export const createGridCell = ( formatters: Record>, DataContext: React.Context ) => ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { - const { table } = useContext(DataContext); + const { table, alignments } = useContext(DataContext); const rowValue = table?.rows[rowIndex][columnId]; const content = formatters[columnId]?.convert(rowValue, 'html'); + const currentAlignment = alignments && alignments[columnId]; + const alignmentClassName = `lnsTableCell--${currentAlignment}`; return ( - ); }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx new file mode 100644 index 00000000000000..e0d31a3ed02012 --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -0,0 +1,112 @@ +/* + * 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 React from 'react'; +import { EuiButtonGroup } from '@elastic/eui'; +import { FramePublicAPI, VisualizationDimensionEditorProps } from '../../types'; +import { DatatableVisualizationState } from '../visualization'; +import { createMockDatasource, createMockFramePublicAPI } from '../../editor_frame_service/mocks'; +import { mountWithIntl } from '@kbn/test/jest'; +import { TableDimensionEditor } from './dimension_editor'; + +describe('data table dimension editor', () => { + let frame: FramePublicAPI; + let state: DatatableVisualizationState; + let setState: (newState: DatatableVisualizationState) => void; + let props: VisualizationDimensionEditorProps; + + function testState(): DatatableVisualizationState { + return { + layerId: 'first', + columns: [ + { + columnId: 'foo', + }, + ], + }; + } + + beforeEach(() => { + state = testState(); + frame = createMockFramePublicAPI(); + frame.datasourceLayers = { + first: createMockDatasource('test').publicAPIMock, + }; + frame.activeData = { + first: { + type: 'datatable', + columns: [ + { + id: 'foo', + name: 'foo', + meta: { + type: 'string', + }, + }, + ], + rows: [], + }, + }; + setState = jest.fn(); + props = { + accessor: 'foo', + frame, + groupId: 'columns', + layerId: 'first', + state, + setState, + }; + }); + + it('should render default alignment', () => { + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('left') + ); + }); + + it('should render default alignment for number', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('right') + ); + }); + + it('should render specific alignment', () => { + state.columns[0].alignment = 'center'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('center') + ); + }); + + it('should set state for the right column', () => { + state.columns = [ + { + columnId: 'foo', + }, + { + columnId: 'bar', + }, + ]; + const instance = mountWithIntl(); + instance.find(EuiButtonGroup).prop('onChange')('center'); + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: [ + { + columnId: 'foo', + alignment: 'center', + }, + { + columnId: 'bar', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 008b805bc8fed3..9c60cd47af3e34 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -7,55 +7,121 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSwitch, EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; import { VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; +const idPrefix = htmlIdGenerator()(); + export function TableDimensionEditor( props: VisualizationDimensionEditorProps ) { - const { state, setState, accessor } = props; - const column = state.columns.find((c) => c.columnId === accessor); + const { state, setState, frame, accessor } = props; + const column = state.columns.find(({ columnId }) => accessor === columnId); - const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; + if (!column) return null; - if (!column) { - return null; - } + // either read config state or use same logic as chart itself + const currentAlignment = + column?.alignment || + (frame.activeData && + frame.activeData[state.layerId].columns.find((col) => col.id === accessor)?.meta.type === + 'number' + ? 'right' + : 'left'); + + const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; return ( - + + ); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss index 5e5db2c6458095..b99ffb6dce8101 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss @@ -1,3 +1,19 @@ .lnsDataTableContainer { height: 100%; } + +.lnsTableCell { + @include euiTextTruncate; +} + +.lnsTableCell--left { + text-align: left; +} + +.lnsTableCell--right { + text-align: right; +} + +.lnsTableCell--center { + text-align: center; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 588340fbe97fa9..22577e8ef5fd31 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -12,7 +12,7 @@ import { EuiDataGrid } from '@elastic/eui'; import { IAggType, IFieldFormat } from 'src/plugins/data/public'; import { EmptyPlaceholder } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; -import { DatatableComponent } from './table_basic'; +import { DataContext, DatatableComponent } from './table_basic'; import { LensMultiTable } from '../../types'; import { DatatableProps } from '../expression'; @@ -427,6 +427,39 @@ describe('DatatableComponent', () => { expect(wrapper.find(EuiDataGrid).prop('columns')!.length).toEqual(2); }); + test('it adds alignment data to context', () => { + const { data, args } = sampleArgs(); + + const wrapper = shallow( + ({ convert: (x) => x } as IFieldFormat)} + dispatchEvent={onDispatchEvent} + getType={jest.fn()} + renderMode="display" + /> + ); + + expect(wrapper.find(DataContext.Provider).prop('value').alignments).toEqual({ + // set via args + a: 'center', + // default for date + b: 'left', + // default for number + c: 'right', + }); + }); + test('it should refresh the table header when the datatable data changes', () => { const { data, args } = sampleArgs(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index f685990f12dd26..e1687ba28f07bb 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -40,7 +40,7 @@ import { createGridSortingConfig, } from './table_actions'; -const DataContext = React.createContext({}); +export const DataContext = React.createContext({}); const gridStyle: EuiDataGridStyle = { border: 'horizontal', @@ -192,6 +192,21 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ] ); + const alignments: Record = useMemo(() => { + const alignmentMap: Record = {}; + columnConfig.columns.forEach((column) => { + if (column.alignment) { + alignmentMap[column.columnId] = column.alignment; + } else { + const isNumeric = + firstLocalTable.columns.find((dataColumn) => dataColumn.id === column.columnId)?.meta + .type === 'number'; + alignmentMap[column.columnId] = isNumeric ? 'right' : 'left'; + } + }); + return alignmentMap; + }, [firstLocalTable, columnConfig]); + const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) { return []; @@ -259,6 +274,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { value={{ table: firstLocalTable, rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions, + alignments, }} > ; } diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 7ead7be67947c9..f6a38541cda271 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -138,6 +138,7 @@ export const datatableColumn: ExpressionFunctionDefinition< inputTypes: ['null'], args: { columnId: { types: ['string'], help: '' }, + alignment: { types: ['string'], help: '' }, hidden: { types: ['boolean'], help: '' }, width: { types: ['number'], help: '' }, }, diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx index ad5e1e552ccd2b..92136c557ad38d 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx @@ -419,11 +419,13 @@ describe('Datatable Visualization', () => { columnId: ['c'], hidden: [], width: [], + alignment: [], }); expect(columnArgs[1].arguments).toEqual({ columnId: ['b'], hidden: [], width: [], + alignment: [], }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 47f8ce09aea68c..fc69c914deb680 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -23,6 +23,7 @@ export interface ColumnState { columnId: string; width?: number; hidden?: boolean; + alignment?: 'left' | 'right' | 'center'; } export interface SortingState { @@ -264,6 +265,7 @@ export const datatableVisualization: Visualization columnId: [column.columnId], hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], width: typeof column.width === 'undefined' ? [] : [column.width], + alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], }, }, ], diff --git a/x-pack/plugins/lens/public/drag_drop/announcements.tsx b/x-pack/plugins/lens/public/drag_drop/announcements.tsx index 61785310bdcf37..3c65008f8f38b7 100644 --- a/x-pack/plugins/lens/public/drag_drop/announcements.tsx +++ b/x-pack/plugins/lens/public/drag_drop/announcements.tsx @@ -11,6 +11,7 @@ export interface HumanData { label: string; groupLabel?: string; position?: number; + nextLabel?: string; } type AnnouncementFunction = (draggedElement: HumanData, dropElement: HumanData) => string; @@ -25,7 +26,7 @@ const selectedTargetReplace = ( { label: dropLabel, groupLabel, position }: HumanData ) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replace', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace {dropLabel} with {label}.`, + defaultMessage: `Replace {dropLabel} in {groupLabel} group at position {position} with {label}. Press space or enter to replace`, values: { label, dropLabel, @@ -34,65 +35,103 @@ const selectedTargetReplace = ( }, }); -const droppedReplace = ({ label }: HumanData, { label: dropLabel, groupLabel }: HumanData) => +const droppedReplace = ( + { label }: HumanData, + { label: dropLabel, groupLabel, position }: HumanData +) => i18n.translate('xpack.lens.dragDrop.announce.duplicated.replace', { - defaultMessage: - 'You have dropped the item. You have replaced {dropLabel} with {label} in {groupLabel} group.', + defaultMessage: 'Replaced {dropLabel} with {label} in {groupLabel} at position {position}', values: { label, dropLabel, groupLabel, + position, }, }); export const announcements: CustomAnnouncementsType = { selectedTarget: { - reorder: ({ label, position: prevPosition }, { position }) => + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => prevPosition === position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reorderedBack', { - defaultMessage: `You have moved the item {label} back to position {prevPosition}`, + defaultMessage: `{label} returned to its initial position {prevPosition}`, values: { label, prevPosition, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reordered', { - defaultMessage: `You have moved the item {label} from position {prevPosition} to position {position}`, + defaultMessage: `Reorder {label} in {groupLabel} group from position {prevPosition} to position {position}. Press space or enter to reorder`, values: { + groupLabel, label, position, prevPosition, }, }), - duplicate_in_group: ({ label }, { label: dropLabel, groupLabel, position }) => + duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.duplicated', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to duplicate {label}.`, + defaultMessage: `Duplicate {label} to {groupLabel} group at position {position}. Press space or enter to duplicate`, values: { label, - dropLabel, groupLabel, position, }, }), field_replace: selectedTargetReplace, replace_compatible: selectedTargetReplace, - replace_incompatible: selectedTargetReplace, + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replaceIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and replace {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace`, + values: { + label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ( + { label }: HumanData, + { label: groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and move to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveCompatible', { + defaultMessage: `Move {label} to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + groupLabel, + position, + }, + }), }, dropped: { - reorder: ({ label, position: prevPosition }, { position }) => + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => i18n.translate('xpack.lens.dragDrop.announce.dropped.reordered', { defaultMessage: - 'You have dropped the item {label}. You have moved the item from position {prevPosition} to positon {position}', + 'Reordered {label} in {groupLabel} group from position {prevPosition} to positon {position}', values: { label, + groupLabel, position, prevPosition, }, }), duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.dropped.duplicated', { - defaultMessage: - 'You have dropped the item. You have duplicated {label} in {groupLabel} group at position {position}', + defaultMessage: 'Duplicated {label} in {groupLabel} group at position {position}', values: { label, groupLabel, @@ -101,7 +140,42 @@ export const announcements: CustomAnnouncementsType = { }), field_replace: droppedReplace, replace_compatible: droppedReplace, - replace_incompatible: droppedReplace, + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.replaceIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and replaced {dropLabel} in {groupLabel} group at position {position}', + values: { + label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ({ label }: HumanData, { groupLabel, position, nextLabel }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and moved to {groupLabel} group at position {position}', + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveCompatible', { + defaultMessage: 'Moved {label} to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }), }, }; @@ -113,13 +187,29 @@ const defaultAnnouncements = { label, }, }), - cancelled: () => - i18n.translate('xpack.lens.dragDrop.announce.cancelled', { - defaultMessage: 'Movement cancelled', - }), + cancelled: ({ label, groupLabel, position }: HumanData) => { + if (!groupLabel || !position) { + return i18n.translate('xpack.lens.dragDrop.announce.cancelled', { + defaultMessage: 'Movement cancelled. {label} returned to its initial position', + values: { + label, + }, + }); + } + return i18n.translate('xpack.lens.dragDrop.announce.cancelledItem', { + defaultMessage: + 'Movement cancelled. {label} returned to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }); + }, + noTarget: () => { return i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.noSelected', { - defaultMessage: `No target selected. Use arrow keys to select a target.`, + defaultMessage: `No target selected. Use arrow keys to select a target`, }); }, @@ -129,17 +219,15 @@ const defaultAnnouncements = { ) => dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.droppedDefault', { - defaultMessage: - 'You have dropped {label} to {dropLabel} in {dropGroupLabel} group at position {position}', + defaultMessage: 'Added {label} in {dropGroupLabel} group at position {position}', values: { label, dropGroupLabel, position, - dropLabel, }, }) : i18n.translate('xpack.lens.dragDrop.announce.droppedNoPosition', { - defaultMessage: 'You have dropped {label} to {dropLabel}', + defaultMessage: 'Added {label} to {dropLabel}', values: { label, dropLabel, @@ -151,16 +239,15 @@ const defaultAnnouncements = { ) => { return dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.default', { - defaultMessage: `Selected {dropLabel} in {dropGroupLabel} group at position {position}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropGroupLabel} group at position {position}. Press space or enter to add`, values: { - dropLabel, label, dropGroupLabel, position, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.defaultNoPosition', { - defaultMessage: `Selected {dropLabel}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropLabel}. Press space or enter to add`, values: { dropLabel, label, diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index f9bae547a563bf..f2a2fda730388d 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -110,7 +110,7 @@ describe('DragDrop', () => { const component = mount( @@ -126,7 +126,7 @@ describe('DragDrop', () => { expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); expect(setDragging).toBeCalledWith(undefined); - expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'label1' } }, 'field_add'); + expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'Label1' } }, 'field_add'); }); test('drop function is not called on dropType undefined', async () => { @@ -138,7 +138,7 @@ describe('DragDrop', () => { const component = mount( @@ -195,7 +195,7 @@ describe('DragDrop', () => { }); test('additional styles are reflected in the className until drop', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClassesOnEnter = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setA11yMessage = jest.fn(); @@ -206,7 +206,7 @@ describe('DragDrop', () => { dragging={dragging} setA11yMessage={setA11yMessage} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} > { }); test('additional enter styles are reflected in the className until dragleave', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClasses = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setActiveDropTarget = jest.fn(); @@ -252,7 +252,7 @@ describe('DragDrop', () => { setA11yMessage={jest.fn()} dragging={dragging} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} setActiveDropTarget={setActiveDropTarget} activeDropTarget={ @@ -303,7 +303,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -326,7 +326,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '3', - humanData: { label: 'label3', position: 1 }, + humanData: { label: 'label3', position: 1, groupLabel: 'Y' }, }, onDrop, dropType: 'replace_compatible' as DropType, @@ -337,7 +337,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '4', - humanData: { label: 'label4', position: 2 }, + humanData: { label: 'label4', position: 2, groupLabel: 'Y' }, }, order: [2, 0, 2, 1], }, @@ -380,11 +380,11 @@ describe('DragDrop', () => { }); keyboardHandler.simulate('keydown', { key: 'Enter' }); expect(setA11yMessage).toBeCalledWith( - 'Selected label3 in group at position 1. Press space or enter to replace label3 with label1.' + 'Replace label3 in Y group at position 1 with Label1. Press space or enter to replace' ); expect(setActiveDropTarget).toBeCalledWith(undefined); expect(onDrop).toBeCalledWith( - { humanData: { label: 'label1', position: 1 }, id: '1' }, + { humanData: { label: 'Label1', position: 1 }, id: '1' }, 'move_compatible' ); }); @@ -437,7 +437,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -488,19 +488,19 @@ describe('DragDrop', () => { const items = [ { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '2', - humanData: { label: 'label2', position: 2 }, + humanData: { label: 'label2', position: 2, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '3', - humanData: { label: 'label3', position: 3 }, + humanData: { label: 'label3', position: 3, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, @@ -583,7 +583,7 @@ describe('DragDrop', () => { }); expect(setDragging).toBeCalledWith({ ...items[0] }); - expect(setA11yMessage).toBeCalledWith('Lifted label1'); + expect(setA11yMessage).toBeCalledWith('Lifted Label1'); expect( component .find('[data-test-subj="lnsDragDrop-reorderableGroup"]') @@ -652,7 +652,7 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(setA11yMessage).toBeCalledWith( - 'You have dropped the item label1. You have moved the item from position 1 to positon 3' + 'Reordered Label1 in X group from position 1 to positon 3' ); expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); @@ -687,7 +687,7 @@ describe('DragDrop', () => { expect(setActiveDropTarget).toBeCalledWith(items[1]); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); }); test(`Keyboard navigation: user can drop element to an activeDropTarget`, () => { @@ -729,13 +729,17 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowDown' }); keyboardHandler.simulate('blur'); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); }); test(`Keyboard Navigation: Reordered elements get extra styles to show the reorder effect`, () => { @@ -772,7 +776,7 @@ describe('DragDrop', () => { component.find('[data-test-subj="lnsDragDrop-translatableDrop"]').at(1).prop('style') ).toEqual(undefined); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); component @@ -837,7 +841,7 @@ describe('DragDrop', () => { keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowUp' }); expect(setActiveDropTarget).toBeCalledWith(undefined); - expect(setA11yMessage).toBeCalledWith('You have moved the item label1 back to position 1'); + expect(setA11yMessage).toBeCalledWith('Label1 returned to its initial position 1'); }); }); }); diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 4b250643203279..6c6a65ab421b33 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -267,7 +267,7 @@ const DragInner = memo(function DragInner({ setDragging(undefined); setActiveDropTarget(undefined); setKeyboardMode(false); - setA11yMessage(announce.cancelled()); + setA11yMessage(announce.cancelled(value.humanData)); if (onDragEnd) { onDragEnd(); } diff --git a/x-pack/plugins/lens/public/drag_drop/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers.tsx index 1a056902a474d6..deb9bf6cb17aec 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers.tsx @@ -204,12 +204,12 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode }

{i18n.translate('xpack.lens.dragDrop.keyboardInstructionsReorder', { - defaultMessage: `Press enter or space to dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press space or enter again to finish.`, })}

{i18n.translate('xpack.lens.dragDrop.keyboardInstructions', { - defaultMessage: `Press enter or space to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press space or enter again to finish.`, })}

diff --git a/x-pack/plugins/lens/public/drag_drop/readme.md b/x-pack/plugins/lens/public/drag_drop/readme.md index 55a9e3157c2471..01cc4c7bc85a52 100644 --- a/x-pack/plugins/lens/public/drag_drop/readme.md +++ b/x-pack/plugins/lens/public/drag_drop/readme.md @@ -56,7 +56,7 @@ const { dragging } = useContext(DragContext); return ( onChange([...items, item])} > {items.map((x) => ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx index f493000aa587a9..1cbd41fff2a8fb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx @@ -64,13 +64,16 @@ export function DraggableDimensionButton({ columnId: string; registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void; }) { - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId, @@ -82,9 +85,10 @@ export function DraggableDimensionButton({ label, groupLabel: group.groupLabel, position: accessorIndex + 1, + nextLabel: nextLabel || '', }, }), - [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel] + [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel, nextLabel] ); // todo: simplify by id and use drop targets? diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx index a83d4bde0383c6..c9d0a7b002870c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx @@ -54,13 +54,16 @@ export function EmptyDimensionButton({ setNewColumnId(generateId()); }, [itemIndex]); - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId: newColumnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId: newColumnId, @@ -72,9 +75,10 @@ export function EmptyDimensionButton({ label, groupLabel: group.groupLabel, position: itemIndex + 1, + nextLabel: nextLabel || '', }, }), - [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex] + [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex, nextLabel] ); return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 1f97399fdd2929..619147987cdd55 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -440,7 +440,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('field_add'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'field_add', + nextLabel: '', + }); const draggingField = { field: { name: 'dragged' }, @@ -459,7 +462,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingField, @@ -492,8 +495,8 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockImplementation(({ columnId }) => - columnId !== 'a' ? 'field_replace' : undefined + mockDatasource.getDropProps.mockImplementation(({ columnId }) => + columnId !== 'a' ? { dropType: 'field_replace', nextLabel: '' } : undefined ); const draggingField = { @@ -513,7 +516,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ columnId: 'a' }) ); @@ -554,7 +557,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('replace_compatible'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'replace_compatible', + nextLabel: '', + }); const draggingOperation = { layerId: 'first', @@ -574,7 +580,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingOperation, diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 36c2d7128460cd..db3b29bb74d317 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -88,7 +88,7 @@ export function createMockDatasource(id: string): DatasourceMock { uniqueLabels: jest.fn((_state) => ({})), renderDimensionTrigger: jest.fn(), renderDimensionEditor: jest.fn(), - getDropTypes: jest.fn(), + getDropProps: jest.fn(), onDrop: jest.fn(), // this is an additional property which doesn't exist on real datasources diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index c26d35c4d9a5d1..5eaa798f459e33 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -8,7 +8,14 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; import React, { ChangeEvent, MouseEvent } from 'react'; import { act } from 'react-dom/test-utils'; -import { EuiComboBox, EuiListGroupItemProps, EuiListGroup, EuiRange } from '@elastic/eui'; +import { + EuiComboBox, + EuiListGroupItemProps, + EuiListGroup, + EuiRange, + EuiSelect, + EuiButtonIcon, +} from '@elastic/eui'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorComponent, @@ -24,8 +31,6 @@ import { OperationMetadata } from '../../types'; import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram'; import { getFieldByNameFactory } from '../pure_helpers'; import { TimeScaling } from './time_scaling'; -import { EuiSelect } from '@elastic/eui'; -import { EuiButtonIcon } from '@elastic/eui'; import { DimensionEditor } from './dimension_editor'; jest.mock('../loader'); @@ -742,6 +747,8 @@ describe('IndexPatternDimensionEditorPanel', () => { }); it('should leave error state when switching from incomplete state to fieldless operation', () => { + // @ts-expect-error + window['__react-beautiful-dnd-disable-dev-warnings'] = true; // issue with enzyme & react-beautiful-dnd throwing errors: https://github.com/atlassian/react-beautiful-dnd/issues/1593 wrapper = mount(); wrapper diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index 1f0381d92ce640..17f069b8831e7b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -6,7 +6,7 @@ */ import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; -import { onDrop, getDropTypes } from './droppable'; +import { onDrop, getDropProps } from './droppable'; import { DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; @@ -91,7 +91,7 @@ const draggingField = { * - Dimension trigger: Not tested here * - Dimension editor component: First half of the tests * - * - getDropTypes: Returns drop types that are possible for the current dragging field or other dimension + * - getDropProps: Returns drop types that are possible for the current dragging field or other dimension * - onDrop: Correct application of drop logic */ describe('IndexPatternDimensionEditorPanel', () => { @@ -174,14 +174,14 @@ describe('IndexPatternDimensionEditorPanel', () => { }); const groupId = 'a'; - describe('getDropTypes', () => { + describe('getDropProps', () => { it('returns undefined if no drag is happening', () => { - expect(getDropTypes({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); + expect(getDropProps({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); }); it('returns undefined if the dragged item has no field', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -198,7 +198,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if field is not supported by filterOperations', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -217,7 +217,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns remove_add if the field is supported by filterOperations and the dropTarget is an existing column', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -226,12 +226,12 @@ describe('IndexPatternDimensionEditorPanel', () => { }, filterOperations: (op: OperationMetadata) => op.dataType === 'number', }) - ).toBe('field_replace'); + ).toEqual({ dropType: 'field_replace', nextLabel: 'Intervals' }); }); it('returns undefined if the field belongs to another index pattern', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -250,7 +250,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if the dragged field is already in use by this operation', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -275,7 +275,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns move if the dragged column is compatible', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -290,7 +290,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }, columnId: 'col2', }) - ).toBe('move_compatible'); + ).toEqual({ dropType: 'move_compatible' }); }); it('returns undefined if the dragged column from different group uses the same field as the dropTarget', () => { @@ -318,7 +318,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -357,7 +357,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -373,7 +373,7 @@ describe('IndexPatternDimensionEditorPanel', () => { columnId: 'col2', filterOperations: (op: OperationMetadata) => op.isBucketed === false, }) - ).toEqual('replace_incompatible'); + ).toEqual({ dropType: 'replace_incompatible', nextLabel: 'Unique count' }); }); }); describe('onDrop', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index 69c7e8c3c2ae61..be791b3c7f7cec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -10,21 +10,29 @@ import { DatasourceDimensionDropHandlerProps, isDraggedOperation, DraggedOperation, + DropType, } from '../../types'; import { IndexPatternColumn } from '../indexpattern'; -import { insertOrReplaceColumn, deleteColumn, getOperationTypesForField } from '../operations'; +import { + insertOrReplaceColumn, + deleteColumn, + getOperationTypesForField, + getOperationDisplay, +} from '../operations'; import { mergeLayer } from '../state_helpers'; import { hasField, isDraggedField } from '../utils'; -import { IndexPatternPrivateState, IndexPatternField, DraggedField } from '../types'; +import { IndexPatternPrivateState, DraggedField } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; type DropHandlerProps = DatasourceDimensionDropHandlerProps & { droppedItem: T; }; -export function getDropTypes( +const operationLabels = getOperationDisplay(); + +export function getDropProps( props: DatasourceDimensionDropProps & { groupId: string } -) { +): { dropType: DropType; nextLabel?: string } | undefined { const { dragging } = props.dragDropContext; if (!dragging) { return; @@ -32,23 +40,27 @@ export function getDropTypes( const layerIndexPatternId = props.state.layers[props.layerId].indexPatternId; - function hasOperationForField(field: IndexPatternField) { - const operationsForNewField = getOperationTypesForField(field, props.filterOperations); - return !!operationsForNewField.length; - } - const currentColumn = props.state.layers[props.layerId].columns[props.columnId]; if (isDraggedField(dragging)) { - if ( - !!(layerIndexPatternId === dragging.indexPatternId && hasOperationForField(dragging.field)) - ) { + const operationsForNewField = getOperationTypesForField(dragging.field, props.filterOperations); + + if (!!(layerIndexPatternId === dragging.indexPatternId && operationsForNewField.length)) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; if (!currentColumn) { - return 'field_add'; + return { dropType: 'field_add', nextLabel: highestPriorityOperationLabel }; } else if ( (hasField(currentColumn) && currentColumn.sourceField !== dragging.field.name) || !hasField(currentColumn) ) { - return 'field_replace'; + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + + return { + dropType: 'field_replace', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; } } return; @@ -62,9 +74,9 @@ export function getDropTypes( // same group if (props.groupId === dragging.groupId) { if (currentColumn) { - return 'reorder'; + return { dropType: 'reorder' }; } - return 'duplicate_in_group'; + return { dropType: 'duplicate_in_group' }; } // compatible group @@ -80,20 +92,34 @@ export function getDropTypes( } if (props.filterOperations(op)) { if (currentColumn) { - return 'replace_compatible'; // in the future also 'swap_compatible' and 'duplicate_compatible' + return { dropType: 'replace_compatible' }; // in the future also 'swap_compatible' and 'duplicate_compatible' } else { - return 'move_compatible'; // in the future also 'duplicate_compatible' + return { dropType: 'move_compatible' }; // in the future also 'duplicate_compatible' } } // suggest const field = hasField(op) && props.state.indexPatterns[layerIndexPatternId].getFieldByName(op.sourceField); - if (field && hasOperationForField(field)) { + const operationsForNewField = field && getOperationTypesForField(field, props.filterOperations); + + if (operationsForNewField && operationsForNewField?.length) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; + if (currentColumn) { - return 'replace_incompatible'; // in the future also 'swap_incompatible', 'duplicate_incompatible' + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + return { + dropType: 'replace_incompatible', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; // in the future also 'swap_incompatible', 'duplicate_incompatible' } else { - return 'move_incompatible'; // in the future also 'duplicate_incompatible' + return { + dropType: 'move_incompatible', + nextLabel: highestPriorityOperationLabel, + }; // in the future also 'duplicate_incompatible' } } } @@ -178,6 +204,12 @@ function onMoveDropToNonCompatibleGroup(props: DropHandlerProps { ); }); - it('should request field stats without a time field, if the index pattern has none', async () => { - indexPattern.timeFieldName = undefined; - core.http.post.mockImplementationOnce(() => { - return Promise.resolve({}); - }); - const wrapper = mountWithIntl(); - - await act(async () => { - clickField(wrapper, 'bytes'); - }); - - expect(core.http.post).toHaveBeenCalledWith( - '/api/lens/index_stats/my-fake-index-pattern/field', - expect.anything() - ); - // Function argument types not detected correctly (https://github.com/microsoft/TypeScript/issues/26591) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { body } = (core.http.post.mock.calls[0] as any)[1]; - expect(JSON.parse(body)).not.toHaveProperty('timeFieldName'); - }); - it('should request field stats every time the button is clicked', async () => { let resolveFunction: (arg: unknown) => void; @@ -150,31 +129,21 @@ describe('IndexPattern Field Item', () => { clickField(wrapper, 'bytes'); - expect(core.http.post).toHaveBeenCalledWith( - `/api/lens/index_stats/my-fake-index-pattern/field`, - { - body: JSON.stringify({ - dslQuery: { - bool: { - must: [{ match_all: {} }], - filter: [], - should: [], - must_not: [], - }, + expect(core.http.post).toHaveBeenCalledWith(`/api/lens/index_stats/1/field`, { + body: JSON.stringify({ + dslQuery: { + bool: { + must: [{ match_all: {} }], + filter: [], + should: [], + must_not: [], }, - fromDate: 'now-7d', - toDate: 'now', - timeFieldName: 'timestamp', - field: { - name: 'bytes', - displayName: 'bytesLabel', - type: 'number', - aggregatable: true, - searchable: true, - }, - }), - } - ); + }, + fromDate: 'now-7d', + toDate: 'now', + fieldName: 'bytes', + }), + }); expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); @@ -227,40 +196,30 @@ describe('IndexPattern Field Item', () => { clickField(wrapper, 'bytes'); expect(core.http.post).toHaveBeenCalledTimes(2); - expect(core.http.post).toHaveBeenLastCalledWith( - `/api/lens/index_stats/my-fake-index-pattern/field`, - { - body: JSON.stringify({ - dslQuery: { - bool: { - must: [], - filter: [ - { - bool: { - should: [{ match_phrase: { 'geo.src': 'US' } }], - minimum_should_match: 1, - }, - }, - { - match: { phrase: { 'geo.dest': 'US' } }, + expect(core.http.post).toHaveBeenLastCalledWith(`/api/lens/index_stats/1/field`, { + body: JSON.stringify({ + dslQuery: { + bool: { + must: [], + filter: [ + { + bool: { + should: [{ match_phrase: { 'geo.src': 'US' } }], + minimum_should_match: 1, }, - ], - should: [], - must_not: [], - }, + }, + { + match: { phrase: { 'geo.dest': 'US' } }, + }, + ], + should: [], + must_not: [], }, - fromDate: 'now-14d', - toDate: 'now-7d', - timeFieldName: 'timestamp', - field: { - name: 'bytes', - displayName: 'bytesLabel', - type: 'number', - aggregatable: true, - searchable: true, - }, - }), - } - ); + }, + fromDate: 'now-14d', + toDate: 'now-7d', + fieldName: 'bytes', + }), + }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index e0198d6d7903e7..e5d46b4a7a0737 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -129,7 +129,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { setState((s) => ({ ...s, isLoading: true })); core.http - .post(`/api/lens/index_stats/${indexPattern.title}/field`, { + .post(`/api/lens/index_stats/${indexPattern.id}/field`, { body: JSON.stringify({ dslQuery: esQuery.buildEsQuery( indexPattern as IIndexPattern, @@ -139,8 +139,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { ), fromDate: dateRange.fromDate, toDate: dateRange.toDate, - timeFieldName: indexPattern.timeFieldName, - field, + fieldName: field.name, }), }) .then((results: FieldStatsResponse) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 6cc89d3dab119f..b8b5eb4c1e6f86 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -31,7 +31,7 @@ import { toExpression } from './to_expression'; import { IndexPatternDimensionTrigger, IndexPatternDimensionEditor, - getDropTypes, + getDropProps, onDrop, } from './dimension_panel'; import { IndexPatternDataPanel } from './datapanel'; @@ -308,7 +308,7 @@ export function getIndexPatternDatasource({ domElement ); }, - getDropTypes, + getDropProps, onDrop, // Reset the temporary invalid state when closing the editor, but don't diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index d8c4c5fd8ca89c..62c729aa2b3f19 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -33,6 +33,19 @@ import { RangePopover } from './advanced_editor'; import { DragDropBuckets } from '../shared_components'; import { getFieldByNameFactory } from '../../../pure_helpers'; +// mocking random id generator function +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + htmlIdGenerator: (fn: unknown) => { + let counter = 0; + return () => counter++; + }, + }; +}); + const dataPluginMockValue = dataPluginMock.createStartContract(); // need to overwrite the formatter field first dataPluginMockValue.fieldFormats.deserialize = jest.fn().mockImplementation(({ params }) => { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 19ce943335a677..419354117eda2e 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -189,9 +189,9 @@ export interface Datasource { renderDimensionTrigger: (domElement: Element, props: DatasourceDimensionTriggerProps) => void; renderDimensionEditor: (domElement: Element, props: DatasourceDimensionEditorProps) => void; renderLayerPanel: (domElement: Element, props: DatasourceLayerPanelProps) => void; - getDropTypes: ( + getDropProps: ( props: DatasourceDimensionDropProps & { groupId: string } - ) => DropType | undefined; + ) => { dropType: DropType; nextLabel?: string } | undefined; onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string }; updateStateOnCloseDimension?: (props: { layerId: string; diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index c6364eca0ff499..3f3e94099f6669 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -61,6 +61,20 @@ describe('existingFields', () => { expect(result).toEqual(['bar']); }); + it('supports runtime fields', () => { + const result = existingFields( + [searchResults({ runtime_foo: ['scriptvalue'] })], + [ + field({ + name: 'runtime_foo', + runtimeField: { type: 'long', script: { source: '2+2' } }, + }), + ] + ); + + expect(result).toEqual(['runtime_foo']); + }); + it('supports meta fields', () => { const result = existingFields( [{ _mymeta: 'abc', ...searchResults({ bar: ['scriptvalue'] }) }], @@ -78,6 +92,11 @@ describe('buildFieldList', () => { typeMeta: 'typemeta', fields: [ { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, + { + name: 'runtime_foo', + isMapped: false, + runtimeField: { type: 'long', script: { source: '2+2' } }, + }, { name: 'bar' }, { name: '@bar' }, { name: 'baz' }, @@ -95,6 +114,14 @@ describe('buildFieldList', () => { }); }); + it('supports runtime fields', () => { + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); + expect(fields.find((f) => f.runtimeField)).toMatchObject({ + name: 'runtime_foo', + runtimeField: { type: 'long', script: { source: '2+2' } }, + }); + }); + it('supports meta fields', () => { const fields = buildFieldList((indexPattern as unknown) as IndexPattern, ['_mymeta']); expect(fields.find((f) => f.isMeta)).toMatchObject({ diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index e76abf4598efa9..8a2db992a839da 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -10,7 +10,7 @@ import { errors } from '@elastic/elasticsearch'; import { schema } from '@kbn/config-schema'; import { RequestHandlerContext, ElasticsearchClient } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; -import { IndexPattern, IndexPatternsService } from 'src/plugins/data/common'; +import { IndexPattern, IndexPatternsService, RuntimeField } from 'src/plugins/data/common'; import { BASE_API_URL } from '../../common'; import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; import { PluginStartContract } from '../plugin'; @@ -30,6 +30,7 @@ export interface Field { isMeta: boolean; lang?: string; script?: string; + runtimeField?: RuntimeField; } export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { @@ -77,11 +78,9 @@ export async function existingFieldsRoute(setup: CoreSetup, if (e.output.statusCode === 404) { return res.notFound({ body: e.output.payload.message }); } - return res.internalError({ body: e.output.payload.message }); + throw new Error(e.output.payload.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } @@ -138,6 +137,7 @@ export function buildFieldList(indexPattern: IndexPattern, metaFields: string[]) // id is a special case - it doesn't show up in the meta field list, // but as it's not part of source, it has to be handled separately. isMeta: metaFields.includes(field.name) || field.name === '_id', + runtimeField: !field.isMapped ? field.runtimeField : undefined, }; }); } @@ -181,6 +181,7 @@ async function fetchIndexPatternStats({ }; const scriptedFields = fields.filter((f) => f.isScript); + const runtimeFields = fields.filter((f) => f.runtimeField); const { body: result } = await client.search({ index, body: { @@ -189,6 +190,11 @@ async function fetchIndexPatternStats({ sort: timeFieldName && fromDate && toDate ? [{ [timeFieldName]: 'desc' }] : [], fields: ['*'], _source: false, + runtime_mappings: runtimeFields.reduce((acc, field) => { + if (!field.runtimeField) return acc; + acc[field.name] = field.runtimeField; + return acc; + }, {} as Record), script_fields: scriptedFields.reduce((acc, field) => { acc[field.name] = { script: { diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 7fd884755d86df..57b3e59f4ad5c1 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -5,12 +5,12 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; import { CoreSetup } from 'src/core/server'; import { IFieldType } from 'src/plugins/data/common'; +import { SavedObjectNotFound } from '../../../../../src/plugins/kibana_utils/common'; import { ESSearchResponse } from '../../../../typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; import { PluginStartContract } from '../plugin'; @@ -21,28 +21,17 @@ export async function initFieldsRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { - path: `${BASE_API_URL}/index_stats/{indexPatternTitle}/field`, + path: `${BASE_API_URL}/index_stats/{indexPatternId}/field`, validate: { params: schema.object({ - indexPatternTitle: schema.string(), + indexPatternId: schema.string(), }), body: schema.object( { dslQuery: schema.object({}, { unknowns: 'allow' }), fromDate: schema.string(), toDate: schema.string(), - timeFieldName: schema.maybe(schema.string()), - field: schema.object( - { - name: schema.string(), - type: schema.string(), - esTypes: schema.maybe(schema.arrayOf(schema.string())), - scripted: schema.maybe(schema.boolean()), - lang: schema.maybe(schema.string()), - script: schema.maybe(schema.string()), - }, - { unknowns: 'allow' } - ), + fieldName: schema.string(), }, { unknowns: 'allow' } ), @@ -50,9 +39,26 @@ export async function initFieldsRoute(setup: CoreSetup) { }, async (context, req, res) => { const requestClient = context.core.elasticsearch.client.asCurrentUser; - const { fromDate, toDate, timeFieldName, field, dslQuery } = req.body; + const { fromDate, toDate, fieldName, dslQuery } = req.body; + + const [{ savedObjects, elasticsearch }, { data }] = await setup.getStartServices(); + const savedObjectsClient = savedObjects.getScopedClient(req); + const esClient = elasticsearch.client.asScoped(req).asCurrentUser; + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + esClient + ); try { + const indexPattern = await indexPatternsService.get(req.params.indexPatternId); + + const timeFieldName = indexPattern.timeFieldName; + const field = indexPattern.fields.find((f) => f.name === fieldName); + + if (!field) { + throw new Error(`Field {fieldName} not found in index pattern ${indexPattern.title}`); + } + const filter = timeFieldName ? [ { @@ -75,11 +81,12 @@ export async function initFieldsRoute(setup: CoreSetup) { const search = async (aggs: unknown) => { const { body: result } = await requestClient.search({ - index: req.params.indexPatternTitle, + index: indexPattern.title, track_total_hits: true, body: { query, aggs, + runtime_mappings: field.runtimeField ? { [fieldName]: field.runtimeField } : {}, }, size: 0, }); @@ -104,6 +111,9 @@ export async function initFieldsRoute(setup: CoreSetup) { body: await getStringSamples(search, field), }); } catch (e) { + if (e instanceof SavedObjectNotFound) { + return res.notFound(); + } if (e instanceof errors.ResponseError && e.statusCode === 404) { return res.notFound(); } @@ -111,11 +121,9 @@ export async function initFieldsRoute(setup: CoreSetup) { if (e.output.statusCode === 404) { return res.notFound(); } - return res.internalError(e.output.message); + throw new Error(e.output.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index cb8cf4b15f8d90..efcde9d14ebbe2 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; @@ -84,11 +83,9 @@ export async function initLensUsageRoute(setup: CoreSetup) if (e.output.statusCode === 404) { return res.notFound(); } - return res.internalError(e.output.message); + throw new Error(e.output.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts index 7fcb73ffeb0086..86f87506dfc2c7 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts @@ -23,18 +23,14 @@ export function registerLicenseRoute({ router, plugins: { licensing } }: RouteDe }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await putLicense({ - acknowledge: Boolean(req.query.acknowledge), - callAsCurrentUser, - licensing, - license: req.body, - }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await putLicense({ + acknowledge: Boolean(req.query.acknowledge), + callAsCurrentUser, + licensing, + license: req.body, + }), + }); } ); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts index c5cd11c022cfe6..dd441051872d2b 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts @@ -16,12 +16,8 @@ export function registerPermissionsRoute({ router.post({ path: addBasePath('/permissions'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await getPermissions({ callAsCurrentUser, isSecurityEnabled }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await getPermissions({ callAsCurrentUser, isSecurityEnabled }), + }); }); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts index 820330c6a12044..bc5fb70f7dadd3 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts @@ -18,17 +18,13 @@ export function registerStartBasicRoute({ router, plugins: { licensing } }: Rout }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await startBasic({ - acknowledge: Boolean(req.query.acknowledge), - callAsCurrentUser, - licensing, - }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await startBasic({ + acknowledge: Boolean(req.query.acknowledge), + callAsCurrentUser, + licensing, + }), + }); } ); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts index 570ae73d8aa613..6986e85e7d280d 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts @@ -12,21 +12,13 @@ import { addBasePath } from '../../helpers'; export function registerStartTrialRoutes({ router, plugins: { licensing } }: RouteDependencies) { router.get({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ body: await canStartTrial(callAsCurrentUser) }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ body: await canStartTrial(callAsCurrentUser) }); }); router.post({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await startTrial({ callAsCurrentUser, licensing }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await startTrial({ callAsCurrentUser, licensing }), + }); }); } diff --git a/x-pack/plugins/logstash/server/routes/cluster/load.ts b/x-pack/plugins/logstash/server/routes/cluster/load.ts index f820ecdbeb4f34..ac7bc245e51ebc 100644 --- a/x-pack/plugins/logstash/server/routes/cluster/load.ts +++ b/x-pack/plugins/logstash/server/routes/cluster/load.ts @@ -29,7 +29,7 @@ export function registerClusterLoadRoute(router: LogstashPluginRouter) { if (err.status === 403) { return response.ok(); } - return response.internalError(); + throw err; } }) ); diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js index c0ad934c232e22..fafafa6b6a0711 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/plugins/maps/public/actions/map_actions.test.js @@ -277,6 +277,9 @@ describe('map_actions', () => { require('../selectors/map_selectors').getSearchSessionId = () => { return searchSessionId; }; + require('../selectors/map_selectors').getSearchSessionMapBuffer = () => { + return undefined; + }; require('../selectors/map_selectors').getMapSettings = () => { return { autoFitToDataBounds: false, diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 33c79c793974b4..9682306852ba92 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -22,6 +22,7 @@ import { getTimeFilters, getLayerList, getSearchSessionId, + getSearchSessionMapBuffer, } from '../selectors/map_selectors'; import { CLEAR_GOTO, @@ -229,12 +230,14 @@ export function setQuery({ filters = [], forceRefresh = false, searchSessionId, + searchSessionMapBuffer, }: { filters?: Filter[]; query?: Query; timeFilters?: TimeRange; forceRefresh?: boolean; searchSessionId?: string; + searchSessionMapBuffer?: MapExtent; }) { return async ( dispatch: ThunkDispatch, @@ -255,6 +258,7 @@ export function setQuery({ }, filters: filters ? filters : getFilters(getState()), searchSessionId, + searchSessionMapBuffer, }; const prevQueryContext = { @@ -262,6 +266,7 @@ export function setQuery({ query: getQuery(getState()), filters: getFilters(getState()), searchSessionId: getSearchSessionId(getState()), + searchSessionMapBuffer: getSearchSessionMapBuffer(getState()), }; if (_.isEqual(nextQueryContext, prevQueryContext)) { diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 89c6d70a217c95..e3a21b596afe14 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -77,7 +77,7 @@ export interface ILayer { canShowTooltip(): boolean; syncLayerWithMB(mbMap: MbMap): void; getLayerTypeIconName(): string; - isDataLoaded(): boolean; + isInitialDataLoadComplete(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getType(): string | undefined; @@ -446,7 +446,7 @@ export class AbstractLayer implements ILayer { throw new Error('should implement Layer#getLayerTypeIconName'); } - isDataLoaded(): boolean { + isInitialDataLoadComplete(): boolean { const sourceDataRequest = this.getSourceDataRequest(); return sourceDataRequest ? sourceDataRequest.hasData() : false; } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 7e87d99fd4f93f..2373ed3ba2062f 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -174,7 +174,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.getValidJoins().length > 0; } - isDataLoaded() { + isInitialDataLoadComplete() { const sourceDataRequest = this.getSourceDataRequest(); if (!sourceDataRequest || !sourceDataRequest.hasData()) { return false; diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index f42a055b24d0a2..b7e50815fd1f71 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -44,6 +44,7 @@ import { } from '../reducers/non_serializable_instances'; import { getMapCenter, + getMapBuffer, getMapZoom, getHiddenLayerIds, getQueryableUniqueIndexPatternIds, @@ -151,11 +152,7 @@ export class MapEmbeddable store.dispatch(disableScrollZoom()); this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: false, - searchSessionId: this.input.searchSessionId, }); if (this.input.refreshConfig) { this._dispatchSetRefreshConfig(this.input.refreshConfig); @@ -230,11 +227,7 @@ export class MapEmbeddable this.input.searchSessionId !== this._prevSearchSessionId ) { this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: false, - searchSessionId: this.input.searchSessionId, }); } @@ -258,30 +251,24 @@ export class MapEmbeddable } } - _dispatchSetQuery({ - query, - timeRange, - filters = [], - forceRefresh, - searchSessionId, - }: { - query?: Query; - timeRange?: TimeRange; - filters?: Filter[]; - forceRefresh: boolean; - searchSessionId?: string; - }) { - this._prevTimeRange = timeRange; - this._prevQuery = query; - this._prevFilters = filters; - this._prevSearchSessionId = searchSessionId; + _dispatchSetQuery({ forceRefresh }: { forceRefresh: boolean }) { + this._prevTimeRange = this.input.timeRange; + this._prevQuery = this.input.query; + this._prevFilters = this.input.filters; + this._prevSearchSessionId = this.input.searchSessionId; + const enabledFilters = this.input.filters + ? this.input.filters.filter((filter) => !filter.meta.disabled) + : []; this._savedMap.getStore().dispatch( setQuery({ - filters: filters.filter((filter) => !filter.meta.disabled), - query, - timeFilters: timeRange, + filters: enabledFilters, + query: this.input.query, + timeFilters: this.input.timeRange, forceRefresh, - searchSessionId, + searchSessionId: this.input.searchSessionId, + searchSessionMapBuffer: getIsRestore(this.input.searchSessionId) + ? this.input.mapBuffer + : undefined, }) ); } @@ -432,11 +419,7 @@ export class MapEmbeddable reload() { this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: true, - searchSessionId: this.input.searchSessionId, }); } @@ -457,6 +440,7 @@ export class MapEmbeddable lon: center.lon, zoom, }, + mapBuffer: getMapBuffer(this._savedMap.getStore().getState()), }); } diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index 67489802bc31d1..7cd4fa8e1253bd 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -12,7 +12,7 @@ import { SavedObjectEmbeddableInput, } from '../../../../../src/plugins/embeddable/public'; import { RefreshInterval, Query, Filter, TimeRange } from '../../../../../src/plugins/data/common'; -import { MapCenterAndZoom } from '../../common/descriptor_types'; +import { MapCenterAndZoom, MapExtent } from '../../common/descriptor_types'; import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; import { MapSettings } from '../reducers/map'; @@ -25,6 +25,7 @@ interface MapEmbeddableState { isLayerTOCOpen?: boolean; openTOCDetails?: string[]; mapCenter?: MapCenterAndZoom; + mapBuffer?: MapExtent; mapSettings?: Partial; hiddenLayers?: string[]; hideFilterActions?: boolean; diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts index 6a51d4feeb9dfe..1cf37561609640 100644 --- a/x-pack/plugins/maps/public/reducers/map.d.ts +++ b/x-pack/plugins/maps/public/reducers/map.d.ts @@ -37,6 +37,7 @@ export type MapContext = { refreshTimerLastTriggeredAt?: string; drawState?: DrawState; searchSessionId?: string; + searchSessionMapBuffer?: MapExtent; }; export type MapSettings = { diff --git a/x-pack/plugins/maps/public/reducers/map.js b/x-pack/plugins/maps/public/reducers/map.js index fa7e1308bac4f4..9bf0df612bac40 100644 --- a/x-pack/plugins/maps/public/reducers/map.js +++ b/x-pack/plugins/maps/public/reducers/map.js @@ -241,7 +241,7 @@ export function map(state = DEFAULT_MAP_STATE, action) { }; return { ...state, mapState: { ...state.mapState, ...newMapState } }; case SET_QUERY: - const { query, timeFilters, filters, searchSessionId } = action; + const { query, timeFilters, filters, searchSessionId, searchSessionMapBuffer } = action; return { ...state, mapState: { @@ -250,6 +250,7 @@ export function map(state = DEFAULT_MAP_STATE, action) { timeFilters, filters, searchSessionId, + searchSessionMapBuffer, }, }; case SET_REFRESH_CONFIG: diff --git a/x-pack/plugins/maps/public/reducers/store.js b/x-pack/plugins/maps/public/reducers/store.js index 76199de5b24c92..f1020b2d6cdb69 100644 --- a/x-pack/plugins/maps/public/reducers/store.js +++ b/x-pack/plugins/maps/public/reducers/store.js @@ -10,7 +10,6 @@ import thunk from 'redux-thunk'; import { ui, DEFAULT_MAP_UI_STATE } from './ui'; import { map, DEFAULT_MAP_STATE } from './map'; // eslint-disable-line import/named import { nonSerializableInstances } from './non_serializable_instances'; -import { MAP_DESTROYED } from '../actions'; export const DEFAULT_MAP_STORE_STATE = { ui: { ...DEFAULT_MAP_UI_STATE }, @@ -26,16 +25,7 @@ export function createMapStore() { nonSerializableInstances, }); - const rootReducer = (state, action) => { - // Reset store on map destroyed - if (action.type === MAP_DESTROYED) { - state = undefined; - } - - return combinedReducers(state, action); - }; - const storeConfig = {}; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - return createStore(rootReducer, storeConfig, composeEnhancers(...enhancers)); + return createStore(combinedReducers, storeConfig, composeEnhancers(...enhancers)); } diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index 89cd80f4daab50..58268b6ea9d82c 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -26,9 +26,67 @@ jest.mock('../kibana_services', () => ({ })); import { DEFAULT_MAP_STORE_STATE } from '../reducers/store'; -import { areLayersLoaded, getTimeFilters } from './map_selectors'; +import { areLayersLoaded, getDataFilters, getTimeFilters } from './map_selectors'; import { LayerDescriptor } from '../../common/descriptor_types'; import { ILayer } from '../classes/layers/layer'; +import { Filter } from '../../../../../src/plugins/data/public'; + +describe('getDataFilters', () => { + const mapExtent = { + maxLat: 1, + maxLon: 1, + minLat: 0, + minLon: 0, + }; + const mapBuffer = { + maxLat: 1.5, + maxLon: 1.5, + minLat: -0.5, + minLon: -0.5, + }; + const mapZoom = 4; + const timeFilters = { to: '2001-01-01', from: '2001-12-31' }; + const refreshTimerLastTriggeredAt = '2001-01-01T00:00:00'; + const query = undefined; + const filters: Filter[] = []; + const searchSessionId = '12345'; + const searchSessionMapBuffer = { + maxLat: 1.25, + maxLon: 1.25, + minLat: -0.25, + minLon: -0.25, + }; + + test('should set buffer as searchSessionMapBuffer when using searchSessionId', () => { + const dataFilters = getDataFilters.resultFunc( + mapExtent, + mapBuffer, + mapZoom, + timeFilters, + refreshTimerLastTriggeredAt, + query, + filters, + searchSessionId, + searchSessionMapBuffer + ); + expect(dataFilters.buffer).toEqual(searchSessionMapBuffer); + }); + + test('should fall back to screen buffer when using searchSessionId and searchSessionMapBuffer is not provided', () => { + const dataFilters = getDataFilters.resultFunc( + mapExtent, + mapBuffer, + mapZoom, + timeFilters, + refreshTimerLastTriggeredAt, + query, + filters, + searchSessionId, + undefined + ); + expect(dataFilters.buffer).toEqual(mapBuffer); + }); +}); describe('getTimeFilters', () => { test('should return timeFilters when contained in state', () => { @@ -66,12 +124,12 @@ describe('getTimeFilters', () => { describe('areLayersLoaded', () => { function createLayerMock({ hasErrors = false, - isDataLoaded = false, + isInitialDataLoadComplete = false, isVisible = true, showAtZoomLevel = true, }: { hasErrors?: boolean; - isDataLoaded?: boolean; + isInitialDataLoadComplete?: boolean; isVisible?: boolean; showAtZoomLevel?: boolean; }) { @@ -79,8 +137,8 @@ describe('areLayersLoaded', () => { hasErrors: () => { return hasErrors; }, - isDataLoaded: () => { - return isDataLoaded; + isInitialDataLoadComplete: () => { + return isInitialDataLoadComplete; }, isVisible: () => { return isVisible; @@ -99,35 +157,37 @@ describe('areLayersLoaded', () => { }); test('layer should not be counted as loaded if it has not loaded', () => { - const layerList = [createLayerMock({ isDataLoaded: false })]; + const layerList = [createLayerMock({ isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(false); }); test('layer should be counted as loaded if its not visible', () => { - const layerList = [createLayerMock({ isVisible: false, isDataLoaded: false })]; + const layerList = [createLayerMock({ isVisible: false, isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if its not shown at zoom level', () => { - const layerList = [createLayerMock({ showAtZoomLevel: false, isDataLoaded: false })]; + const layerList = [ + createLayerMock({ showAtZoomLevel: false, isInitialDataLoadComplete: false }), + ]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if it has a loading error', () => { - const layerList = [createLayerMock({ hasErrors: true, isDataLoaded: false })]; + const layerList = [createLayerMock({ hasErrors: true, isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if its loaded', () => { - const layerList = [createLayerMock({ isDataLoaded: true })]; + const layerList = [createLayerMock({ isInitialDataLoadComplete: true })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index b16ac704c3715b..35c73a2bd2f1c7 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -183,6 +183,9 @@ export const getFilters = ({ map }: MapStoreState): Filter[] => map.mapState.fil export const getSearchSessionId = ({ map }: MapStoreState): string | undefined => map.mapState.searchSessionId; +export const getSearchSessionMapBuffer = ({ map }: MapStoreState): MapExtent | undefined => + map.mapState.searchSessionMapBuffer; + export const isUsingSearch = (state: MapStoreState): boolean => { const filters = getFilters(state).filter((filter) => !filter.meta.disabled); const queryString = _.get(getQuery(state), 'query', ''); @@ -235,6 +238,7 @@ export const getDataFilters = createSelector( getQuery, getFilters, getSearchSessionId, + getSearchSessionMapBuffer, ( mapExtent, mapBuffer, @@ -243,11 +247,12 @@ export const getDataFilters = createSelector( refreshTimerLastTriggeredAt, query, filters, - searchSessionId + searchSessionId, + searchSessionMapBuffer ) => { return { extent: mapExtent, - buffer: mapBuffer, + buffer: searchSessionId && searchSessionMapBuffer ? searchSessionMapBuffer : mapBuffer, zoom: mapZoom, timeFilters, refreshTimerLastTriggeredAt, @@ -432,7 +437,7 @@ export const areLayersLoaded = createSelector( layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.hasErrors() && - !layer.isDataLoaded() + !layer.isInitialDataLoadComplete() ) { return false; } diff --git a/x-pack/plugins/ml/common/types/alerts.ts b/x-pack/plugins/ml/common/types/alerts.ts index 51d06b99062300..7e6e9d89c5a651 100644 --- a/x-pack/plugins/ml/common/types/alerts.ts +++ b/x-pack/plugins/ml/common/types/alerts.ts @@ -15,7 +15,7 @@ export type TopHitsResultsKeys = 'top_record_hits' | 'top_bucket_hits' | 'top_in export interface AlertExecutionResult { count: number; key: number; - key_as_string: string; + alertInstanceKey: string; isInterim: boolean; jobIds: string[]; timestamp: number; @@ -47,10 +47,13 @@ interface BaseAnomalyAlertDoc { export interface RecordAnomalyAlertDoc extends BaseAnomalyAlertDoc { result_type: typeof ANOMALY_RESULT_TYPE.RECORD; function: string; - field_name: string; - by_field_value: string | number; - over_field_value: string | number; - partition_field_value: string | number; + field_name?: string; + by_field_name?: string; + by_field_value?: string | number; + over_field_name?: string; + over_field_value?: string | number; + partition_field_name?: string; + partition_field_value?: string | number; } export interface BucketAnomalyAlertDoc extends BaseAnomalyAlertDoc { diff --git a/x-pack/plugins/ml/common/types/fields.ts b/x-pack/plugins/ml/common/types/fields.ts index 581ce861e8331c..047852534965c2 100644 --- a/x-pack/plugins/ml/common/types/fields.ts +++ b/x-pack/plugins/ml/common/types/fields.ts @@ -113,7 +113,7 @@ type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; export interface RuntimeField { type: RuntimeType; - script: + script?: | string | { source: string; diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.ts b/x-pack/plugins/ml/common/util/anomaly_utils.ts index 028afee2524c93..68605f29c7be96 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.ts +++ b/x-pack/plugins/ml/common/util/anomaly_utils.ts @@ -230,8 +230,6 @@ export function getEntityFieldName(record: AnomalyRecordDoc): string | undefined if (record.partition_field_name !== undefined) { return record.partition_field_name; } - - return undefined; } // Returns the value of the field to use as the entity value from the source record @@ -249,8 +247,6 @@ export function getEntityFieldValue(record: AnomalyRecordDoc): string | number | if (record.partition_field_value !== undefined) { return record.partition_field_value; } - - return undefined; } // Returns the list of partitioning entity fields for the source record as a list diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx index 469327323bee98..fc8798a3af0f57 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx @@ -131,20 +131,6 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ } if (isRegOrClassJob) { - if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { - advancedFirstCol.push({ - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', { - defaultMessage: 'Top classes', - }), - description: - numTopClasses === -1 - ? i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.allClasses', { - defaultMessage: 'All classes', - }) - : getStringValue(numTopClasses), - }); - } - advancedFirstCol.push({ title: i18n.translate( 'xpack.ml.dataframe.analytics.create.configDetails.numTopFeatureImportanceValues', @@ -170,15 +156,23 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ } ); - advancedSecondCol.push({ - title: i18n.translate( - 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName', - { - defaultMessage: 'Prediction field name', - } - ), - description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`, - }); + advancedSecondCol.push( + { + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName', + { + defaultMessage: 'Prediction field name', + } + ), + description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`, + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', { + defaultMessage: 'Randomized seed', + }), + description: getStringValue(randomizeSeed), + } + ); hyperSecondCol.push( { @@ -205,20 +199,26 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ description: `${modelMemoryLimit}`, }); - hyperThirdCol.push( - { - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', { - defaultMessage: 'Gamma', - }), - description: getStringValue(gamma), - }, - { - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', { - defaultMessage: 'Randomized seed', - }), - description: getStringValue(randomizeSeed), - } - ); + hyperThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', { + defaultMessage: 'Gamma', + }), + description: getStringValue(gamma), + }); + } + + if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { + advancedThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', { + defaultMessage: 'Top classes', + }), + description: + numTopClasses === -1 + ? i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.allClasses', { + defaultMessage: 'All classes', + }) + : getStringValue(numTopClasses), + }); } if (maxNumThreads !== undefined) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx index 8e25fc961c7c28..71770dcf952d97 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx @@ -409,6 +409,34 @@ export const AdvancedStepForm: FC = ({ /> + + + + setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value }) + } + isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'} + value={getNumberValue(randomizeSeed)} + step={1} + /> + + ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx index cd4dd44edfa507..704b2cc77a7f93 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx @@ -31,7 +31,6 @@ export const HyperParameters: FC = ({ actions, state, advancedParamErrors lambda, maxOptimizationRoundsPerHyperparameter, maxTrees, - randomizeSeed, softTreeDepthLimit, softTreeDepthTolerance, } = state.form; @@ -184,34 +183,6 @@ export const HyperParameters: FC = ({ actions, state, advancedParamErrors /> - - - - setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value }) - } - isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'} - value={getNumberValue(randomizeSeed)} - step={1} - /> - - { try { - return await mlJobService.saveNewDatafeed(this._datafeed_config, this._job_config.job_id); + const tempDatafeed = this._getDatafeedWithFilteredRuntimeMappings(); + return await mlJobService.saveNewDatafeed(tempDatafeed, this._job_config.job_id); } catch (error) { throw error; } @@ -559,6 +562,23 @@ export class JobCreator { return jobRunner; } + private _getDatafeedWithFilteredRuntimeMappings(): Datafeed { + if (this._filterRuntimeMappingsOnSave === false) { + return this._datafeed_config; + } + + const { runtime_mappings: filteredRuntimeMappings } = filterRuntimeMappings( + this._job_config, + this._datafeed_config + ); + + return { + ...this._datafeed_config, + runtime_mappings: + Object.keys(filteredRuntimeMappings).length > 0 ? filteredRuntimeMappings : undefined, + }; + } + public subscribeToProgress(func: ProgressSubscriber) { this._subscribers.push(func); } @@ -645,6 +665,14 @@ export class JobCreator { return JSON.stringify(this._datafeed_config, null, 2); } + public set filterRuntimeMappingsOnSave(filter: boolean) { + this._filterRuntimeMappingsOnSave = filter; + } + + public get filterRuntimeMappingsOnSave(): boolean { + return this._filterRuntimeMappingsOnSave; + } + protected _initPerPartitionCategorization() { if (this._job_config.analysis_config.per_partition_categorization === undefined) { this._job_config.analysis_config.per_partition_categorization = {}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts new file mode 100644 index 00000000000000..43e7d4e45b6e0c --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts @@ -0,0 +1,183 @@ +/* + * 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 { Job, Datafeed } from '../../../../../../../common/types/anomaly_detection_jobs'; +import { filterRuntimeMappings } from './filter_runtime_mappings'; + +function getJob(): Job { + return { + job_id: 'test', + description: '', + groups: [], + analysis_config: { + bucket_span: '15m', + detectors: [ + { + function: 'mean', + field_name: 'responsetime', + }, + ], + influencers: [], + }, + data_description: { + time_field: '@timestamp', + }, + analysis_limits: { + model_memory_limit: '11MB', + }, + model_plot_config: { + enabled: false, + annotations_enabled: false, + }, + }; +} + +function getDatafeed(): Datafeed { + return { + datafeed_id: 'datafeed-test', + job_id: 'dds', + indices: ['farequote-*'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: { + responsetime_big: { + type: 'double', + script: { + source: "emit(doc['responsetime'].value * 100.0)", + }, + }, + airline_lower: { + type: 'keyword', + script: { + source: "emit(doc['airline'].value.toLowerCase())", + }, + }, + }, + }; +} + +function getAggs() { + return { + buckets: { + date_histogram: { + field: '@timestamp', + fixed_interval: '90000ms', + }, + aggregations: { + responsetime: { + avg: { + field: 'responsetime_big', + }, + }, + '@timestamp': { + max: { + field: '@timestamp', + }, + }, + }, + }, + }; +} + +describe('filter_runtime_mappings', () => { + describe('filterRuntimeMappings()', () => { + let job: Job; + let datafeed: Datafeed; + beforeEach(() => { + job = getJob(); + datafeed = getDatafeed(); + }); + + test('returns no runtime mappings, no mappings in aggs', () => { + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(2); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('returns no runtime mappings, no runtime mappings in datafeed', () => { + datafeed.runtime_mappings = undefined; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + expect(resp.runtime_mappings.responsetime_big).toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(0); + expect(resp.discarded_mappings.airline_lower).toEqual(undefined); + }); + + test('return one runtime mapping and one unused mapping, mappings in aggs', () => { + datafeed.aggregations = getAggs(); + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return no runtime mappings, no mappings in aggs', () => { + datafeed.aggregations = getAggs(); + datafeed.aggregations!.buckets!.aggregations!.responsetime!.avg!.field! = 'responsetime'; + + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(2); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return one runtime mapping and one unused mapping, no mappings in aggs', () => { + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = 'responsetime_big'; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return two runtime mappings, no mappings in aggs', () => { + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = 'responsetime_big'; + // set the detector by field to be a runtime mapping + job.analysis_config.detectors[0].by_field_name = 'airline_lower'; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(2); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.runtime_mappings.airline_lower).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(0); + }); + + test('return two runtime mappings, no mappings in aggs, categorization job', () => { + job.analysis_config.detectors[0].function = 'count'; + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = undefined; + // set the detector by field to be a runtime mapping + job.analysis_config.detectors[0].by_field_name = 'mlcategory'; + job.analysis_config.categorization_field_name = 'airline_lower'; + + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.airline_lower).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts new file mode 100644 index 00000000000000..5319cd3c3aabc9 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts @@ -0,0 +1,104 @@ +/* + * 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. + */ +/* + * 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 { RuntimeMappings } from '../../../../../../../common/types/fields'; +import type { Datafeed, Job } from '../../../../../../../common/types/anomaly_detection_jobs'; + +interface Response { + runtime_mappings: RuntimeMappings; + discarded_mappings: RuntimeMappings; +} + +export function filterRuntimeMappings(job: Job, datafeed: Datafeed): Response { + if (datafeed.runtime_mappings === undefined) { + return { + runtime_mappings: {}, + discarded_mappings: {}, + }; + } + + const usedFields = findFieldsInJob(job, datafeed); + + const { runtimeMappings, discardedMappings } = createMappings( + datafeed.runtime_mappings, + usedFields + ); + + return { runtime_mappings: runtimeMappings, discarded_mappings: discardedMappings }; +} + +function findFieldsInJob(job: Job, datafeed: Datafeed) { + const usedFields = new Set(); + job.analysis_config.detectors.forEach((d) => { + if (d.field_name !== undefined) { + usedFields.add(d.field_name); + } + if (d.by_field_name !== undefined) { + usedFields.add(d.by_field_name); + } + if (d.over_field_name !== undefined) { + usedFields.add(d.over_field_name); + } + if (d.partition_field_name !== undefined) { + usedFields.add(d.partition_field_name); + } + }); + + if (job.analysis_config.categorization_field_name !== undefined) { + usedFields.add(job.analysis_config.categorization_field_name); + } + + if (job.analysis_config.summary_count_field_name !== undefined) { + usedFields.add(job.analysis_config.summary_count_field_name); + } + + if (job.analysis_config.influencers !== undefined) { + job.analysis_config.influencers.forEach((i) => usedFields.add(i)); + } + + const aggs = datafeed.aggregations ?? datafeed.aggs; + if (aggs !== undefined) { + findFieldsInAgg(aggs).forEach((f) => usedFields.add(f)); + } + + return [...usedFields]; +} + +function findFieldsInAgg(obj: Record) { + const fields: string[] = []; + Object.entries(obj).forEach(([key, val]) => { + if (typeof val === 'object' && val !== null) { + fields.push(...findFieldsInAgg(val)); + } else if (typeof val === 'string' && key === 'field') { + fields.push(val); + } + }); + return fields; +} + +function createMappings(rm: RuntimeMappings, usedFieldNames: string[]) { + return { + runtimeMappings: usedFieldNames.reduce((acc, cur) => { + if (rm[cur] !== undefined) { + acc[cur] = rm[cur]; + } + return acc; + }, {} as RuntimeMappings), + discardedMappings: Object.keys(rm).reduce((acc, cur) => { + if (usedFieldNames.includes(cur) === false && rm[cur] !== undefined) { + acc[cur] = rm[cur]; + } + return acc; + }, {} as RuntimeMappings), + }; +} diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts index 261fac7b620ba9..f029fa24f96078 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { resolveTimeInterval } from './alerting_service'; +import { resolveBucketSpanInSeconds } from './alerting_service'; describe('Alerting Service', () => { test('should resolve maximum bucket interval', () => { - expect(resolveTimeInterval(['15m', '1h', '6h', '90s'])).toBe('43200s'); + expect(resolveBucketSpanInSeconds(['15m', '1h', '6h', '90s'])).toBe(43200); }); }); diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 5ef883cc50fbb3..6e7cd77e450bcc 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -7,6 +7,9 @@ import Boom from '@hapi/boom'; import rison from 'rison-node'; +import { ElasticsearchClient } from 'kibana/server'; +import moment from 'moment'; +import { Duration } from 'moment/moment'; import { MlClient } from '../ml_client'; import { MlAnomalyDetectionAlertParams, @@ -25,6 +28,8 @@ import { import { parseInterval } from '../../../common/util/parse_interval'; import { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type'; import { MlJobsResponse } from '../../../common/types/job_service'; +import { ANOMALY_SCORE_MATCH_GROUP_ID } from '../../../common/constants/alerts'; +import { getEntityFieldName, getEntityFieldValue } from '../../../common/util/anomaly_utils'; function isDefined(argument: T | undefined | null): argument is T { return argument !== undefined && argument !== null; @@ -34,22 +39,23 @@ function isDefined(argument: T | undefined | null): argument is T { * Resolves the longest bucket span from the list and multiply it by 2. * @param bucketSpans Collection of bucket spans */ -export function resolveTimeInterval(bucketSpans: string[]): string { - return `${ +export function resolveBucketSpanInSeconds(bucketSpans: string[]): number { + return ( Math.max( ...bucketSpans .map((b) => parseInterval(b)) .filter(isDefined) .map((v) => v.asSeconds()) ) * 2 - }s`; + ); } /** * Alerting related server-side methods * @param mlClient + * @param esClient */ -export function alertingServiceProvider(mlClient: MlClient) { +export function alertingServiceProvider(mlClient: MlClient, esClient: ElasticsearchClient) { const getAggResultsLabel = (resultType: AnomalyResultType) => { return { aggGroupLabel: `${resultType}_results` as PreviewResultsKeys, @@ -177,10 +183,14 @@ export function alertingServiceProvider(mlClient: MlClient) { 'is_interim', 'function', 'field_name', + 'by_field_name', 'by_field_value', + 'over_field_name', 'over_field_value', + 'partition_field_name', 'partition_field_value', 'job_id', + 'detector_index', ], }, size: 3, @@ -257,14 +267,31 @@ export function alertingServiceProvider(mlClient: MlClient) { }; }; + /** + * Provides unique key for the anomaly result. + */ + const getAlertInstanceKey = (source: any): string => { + let alertInstanceKey = `${source.job_id}_${source.timestamp}`; + if (source.result_type === ANOMALY_RESULT_TYPE.INFLUENCER) { + alertInstanceKey += `_${source.influencer_field_name}_${source.influencer_field_value}`; + } else if (source.result_type === ANOMALY_RESULT_TYPE.RECORD) { + const fieldName = getEntityFieldName(source); + const fieldValue = getEntityFieldValue(source); + alertInstanceKey += `_${source.detector_index}_${source.function}_${fieldName}_${fieldValue}`; + } + return alertInstanceKey; + }; + /** * Builds a request body - * @param params - * @param previewTimeInterval + * @param params - Alert params + * @param previewTimeInterval - Relative time interval to test the alert condition + * @param checkIntervalGap - Interval between alert executions */ const fetchAnomalies = async ( params: MlAnomalyDetectionAlertParams, - previewTimeInterval?: string + previewTimeInterval?: string, + checkIntervalGap?: Duration ): Promise => { const jobAndGroupIds = [ ...(params.jobSelection.jobIds ?? []), @@ -281,9 +308,14 @@ export function alertingServiceProvider(mlClient: MlClient) { return; } - const lookBackTimeInterval = resolveTimeInterval( - jobsResponse.map((v) => v.analysis_config.bucket_span) - ); + /** + * The check interval might be bigger than the 2x bucket span. + * We need to check the biggest time range to make sure anomalies are not missed. + */ + const lookBackTimeInterval = `${Math.max( + resolveBucketSpanInSeconds(jobsResponse.map((v) => v.analysis_config.bucket_span)), + checkIntervalGap ? checkIntervalGap.asSeconds() : 0 + )}s`; const jobIds = jobsResponse.map((v) => v.job_id); @@ -370,19 +402,22 @@ export function alertingServiceProvider(mlClient: MlClient) { const aggTypeResults = v[resultsLabel.aggGroupLabel]; const requestedAnomalies = aggTypeResults[resultsLabel.topHitsLabel].hits.hits; + const topAnomaly = requestedAnomalies[0]; + const alertInstanceKey = getAlertInstanceKey(topAnomaly._source); + return { count: aggTypeResults.doc_count, key: v.key, - key_as_string: v.key_as_string, + alertInstanceKey, jobIds: [...new Set(requestedAnomalies.map((h) => h._source.job_id))], isInterim: requestedAnomalies.some((h) => h._source.is_interim), - timestamp: requestedAnomalies[0]._source.timestamp, - timestampIso8601: requestedAnomalies[0].fields.timestamp_iso8601[0], - timestampEpoch: requestedAnomalies[0].fields.timestamp_epoch[0], - score: requestedAnomalies[0].fields.score[0], + timestamp: topAnomaly._source.timestamp, + timestampIso8601: topAnomaly.fields.timestamp_iso8601[0], + timestampEpoch: topAnomaly.fields.timestamp_epoch[0], + score: topAnomaly.fields.score[0], bucketRange: { - start: requestedAnomalies[0].fields.start[0], - end: requestedAnomalies[0].fields.end[0], + start: topAnomaly.fields.start[0], + end: topAnomaly.fields.end[0], }, topRecords: v.record_results.top_record_hits.hits.hits.map((h) => ({ ...h._source, @@ -479,13 +514,24 @@ export function alertingServiceProvider(mlClient: MlClient) { /** * Return the result of an alert condition execution. * - * @param params + * @param params - Alert params + * @param publicBaseUrl + * @param alertId - Alert ID + * @param startedAt + * @param previousStartedAt */ execute: async ( params: MlAnomalyDetectionAlertParams, - publicBaseUrl: string | undefined + publicBaseUrl: string | undefined, + alertId: string, + startedAt: Date, + previousStartedAt: Date | null ): Promise => { - const res = await fetchAnomalies(params); + const checkIntervalGap = previousStartedAt + ? moment.duration(moment(startedAt).diff(previousStartedAt)) + : undefined; + + const res = await fetchAnomalies(params, undefined, checkIntervalGap); if (!res) { throw new Error('No results found'); @@ -496,12 +542,65 @@ export function alertingServiceProvider(mlClient: MlClient) { const anomalyExplorerUrl = buildExplorerUrl(result, params.resultType as AnomalyResultType); - return { + const executionResult = { ...result, - name: result.key_as_string, + name: result.alertInstanceKey, anomalyExplorerUrl, kibanaBaseUrl: publicBaseUrl!, }; + + let kibanaEventLogCount = 0; + try { + // Check kibana-event-logs for presence of this alert instance + const kibanaLogResults = await esClient.count({ + index: '.kibana-event-log-*', + body: { + query: { + bool: { + must: [ + { + term: { + 'kibana.alerting.action_group_id': { + value: ANOMALY_SCORE_MATCH_GROUP_ID, + }, + }, + }, + { + term: { + 'kibana.alerting.instance_id': { + value: executionResult.name, + }, + }, + }, + { + nested: { + path: 'kibana.saved_objects', + query: { + term: { + 'kibana.saved_objects.id': { + value: alertId, + }, + }, + }, + }, + }, + ], + }, + }, + }, + }); + + kibanaEventLogCount = kibanaLogResults.body.count; + } catch (e) { + // eslint-disable-next-line no-console + console.log('Unable to check kibana event logs', e); + } + + if (kibanaEventLogCount > 0) { + return; + } + + return executionResult; }, /** * Checks how often the alert condition will fire an alert instance diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 6f8fa59aa231e7..30a92c02cefc3a 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -123,13 +123,19 @@ export function registerAnomalyDetectionAlertType({ }, producer: PLUGIN_ID, minimumLicenseRequired: MINIMUM_FULL_LICENSE, - async executor({ services, params }) { + async executor({ services, params, alertId, state, previousStartedAt, startedAt }) { const fakeRequest = {} as KibanaRequest; const { execute } = mlSharedServices.alertingServiceProvider( services.savedObjectsClient, fakeRequest ); - const executionResult = await execute(params, publicBaseUrl); + const executionResult = await execute( + params, + publicBaseUrl, + alertId, + startedAt, + previousStartedAt + ); if (executionResult) { const alertInstanceName = executionResult.name; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts index 5c9106d78595fc..371c5435f91de9 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts @@ -5,12 +5,14 @@ * 2.0. */ +import { Logger } from 'kibana/server'; import { AlertingPlugin } from '../../../../alerts/server'; import { registerAnomalyDetectionAlertType } from './register_anomaly_detection_alert_type'; import { SharedServices } from '../../shared_services'; export interface RegisterAlertParams { alerts: AlertingPlugin['setup']; + logger: Logger; mlSharedServices: SharedServices; publicBaseUrl: string | undefined; } diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index d075f0cc4e6605..278dd19f74acc3 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -196,9 +196,6 @@ export function getMlClient( await jobIdsCheck('data-frame-analytics', p); return mlClient.explainDataFrameAnalytics(...p); }, - async findFileStructure(...p: Parameters) { - return mlClient.findFileStructure(...p); - }, async flushJob(...p: Parameters) { await jobIdsCheck('anomaly-detector', p); return mlClient.flushJob(...p); diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index 6618e19dfe2663..7ff1acf4ac0ce2 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -29,7 +29,6 @@ export type MlClientParams = | Parameters | Parameters | Parameters - | Parameters | Parameters | Parameters | Parameters diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 2dd3373f2e42f5..6e57e997e5f008 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -5,20 +5,22 @@ * 2.0. */ +import { IScopedClusterClient } from 'kibana/server'; import { AnalysisResult, FormattedOverrides, InputOverrides, FindFileStructureResponse, } from '../../../common/types/file_datavisualizer'; -import type { MlClient } from '../../lib/ml_client'; export type InputData = any[]; -export function fileDataVisualizerProvider(mlClient: MlClient) { +export function fileDataVisualizerProvider(client: IScopedClusterClient) { async function analyzeFile(data: InputData, overrides: InputOverrides): Promise { overrides.explain = overrides.explain === undefined ? 'true' : overrides.explain; - const { body } = await mlClient.findFileStructure({ + const { + body, + } = await client.asInternalUser.textStructure.findStructure({ body: data, ...overrides, }); diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 10ed70d7f73969..24fac9184cc27c 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -211,6 +211,7 @@ export class MlServerPlugin if (plugins.alerts) { registerMlAlerts({ alerts: plugins.alerts, + logger: this.log, mlSharedServices: sharedServices, publicBaseUrl: coreSetup.http.basePath.publicBaseUrl, }); diff --git a/x-pack/plugins/ml/server/routes/alerting.ts b/x-pack/plugins/ml/server/routes/alerting.ts index 7b7f3a7db9723a..a268a5200b35e7 100644 --- a/x-pack/plugins/ml/server/routes/alerting.ts +++ b/x-pack/plugins/ml/server/routes/alerting.ts @@ -30,9 +30,9 @@ export function alertingRoutes({ router, routeGuard }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { + routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response, client }) => { try { - const alertingService = alertingServiceProvider(mlClient); + const alertingService = alertingServiceProvider(mlClient, client.asInternalUser); const result = await alertingService.preview(request.body); diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index fa56a6854de62d..6b200c59f57d5a 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IScopedClusterClient } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { MAX_FILE_SIZE_BYTES } from '../../../file_upload/common'; import { InputOverrides } from '../../common/types/file_datavisualizer'; @@ -13,10 +14,9 @@ import { InputData, fileDataVisualizerProvider } from '../models/file_data_visua import { RouteInitialization } from '../types'; import { analyzeFileQuerySchema } from './schemas/file_data_visualizer_schema'; -import type { MlClient } from '../lib/ml_client'; -function analyzeFiles(mlClient: MlClient, data: InputData, overrides: InputOverrides) { - const { analyzeFile } = fileDataVisualizerProvider(mlClient); +function analyzeFiles(client: IScopedClusterClient, data: InputData, overrides: InputOverrides) { + const { analyzeFile } = fileDataVisualizerProvider(client); return analyzeFile(data, overrides); } @@ -48,9 +48,9 @@ export function fileDataVisualizerRoutes({ router, routeGuard }: RouteInitializa tags: ['access:ml:canFindFileStructure'], }, }, - routeGuard.basicLicenseAPIGuard(async ({ mlClient, request, response }) => { + routeGuard.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const result = await analyzeFiles(mlClient, request.body, request.query); + const result = await analyzeFiles(client, request.body, request.query); return response.ok({ body: result }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts index 318dac200a8772..cbe22478e12d6c 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts @@ -20,7 +20,9 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canGetJobs']) - .ok(({ mlClient }) => alertingServiceProvider(mlClient).preview(...args)); + .ok(({ mlClient, scopedClient }) => + alertingServiceProvider(mlClient, scopedClient.asInternalUser).preview(...args) + ); }, execute: async ( ...args: Parameters @@ -28,7 +30,9 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canGetJobs']) - .ok(({ mlClient }) => alertingServiceProvider(mlClient).execute(...args)); + .ok(({ mlClient, scopedClient }) => + alertingServiceProvider(mlClient, scopedClient.asInternalUser).execute(...args) + ); }, }; }, diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 8b1e91e2caf67e..18c96106566486 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -580,6 +580,11 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; */ export const ALERT_ACTION_TYPE_LOG = '.server-log'; +/** + * To enable modifing of alerts in under actions + */ +export const ALERT_REQUIRES_APP_CONTEXT = false; + export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo']; /** diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index f0b58b41015052..3088e2a75c3492 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -9,7 +9,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Expression, Props } from '../components/param_details_form/expression'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; -import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; +import { + ALERT_CCR_READ_EXCEPTIONS, + ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; import { AlertTypeParams } from '../../../../alerts/common'; interface ValidateOptions extends AlertTypeParams { @@ -45,6 +49,6 @@ export function createCCRReadExceptionsAlertType(): AlertTypeModel { return { @@ -26,6 +30,6 @@ export function createDiskUsageAlertType(): AlertTypeModel ), validate, defaultActionMessage: '{{context.internalFullMessage}}', - requiresAppContext: true, + requiresAppContext: ALERT_REQUIRES_APP_CONTEXT, }; } diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index 3f23035f526496..87850f893797a2 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -10,7 +10,11 @@ import { i18n } from '@kbn/i18n'; import { EuiTextColor, EuiSpacer } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { LEGACY_ALERTS, LEGACY_ALERT_DETAILS } from '../../../common/constants'; +import { + LEGACY_ALERTS, + LEGACY_ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; export function createLegacyAlertTypes(): AlertTypeModel[] { return LEGACY_ALERTS.map((legacyAlert) => { @@ -34,7 +38,7 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { ), defaultActionMessage: '{{context.internalFullMessage}}', validate: () => ({ errors: {} }), - requiresAppContext: true, + requiresAppContext: ALERT_REQUIRES_APP_CONTEXT, }; }); } diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 8437faf2258b97..6639b4f2f6a489 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -11,7 +11,11 @@ import { Expression, Props } from '../components/param_details_form/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; +import { + ALERT_MEMORY_USAGE, + ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; export function createMemoryUsageAlertType(): AlertTypeModel { return { @@ -26,6 +30,6 @@ export function createMemoryUsageAlertType(): AlertTypeModel { }); it('should fetch from the alerts client', async () => { - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status).toEqual({ monitoring_alert_cpu_usage: { rawAlert: { id: 1 }, @@ -99,24 +96,18 @@ describe('fetchStatus', () => { }, ]; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(Object.values(status).length).toBe(1); expect(Object.keys(status)).toEqual(alertTypes); expect(status[alertType].states[0].state.ui.isFiring).toBe(true); }); it('should pass in the right filter to the alerts client', async () => { - await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( `alert.attributes.alertTypeId:${alertType}` ); @@ -127,12 +118,9 @@ describe('fetchStatus', () => { alertTypeState: null, })) as any; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status[alertType].states.length).toEqual(0); }); @@ -142,12 +130,9 @@ describe('fetchStatus', () => { data: [], })) as any; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status).toEqual({}); }); @@ -163,7 +148,7 @@ describe('fetchStatus', () => { alertsClient as any, customLicenseService as any, [ALERT_CLUSTER_HEALTH], - defaultClusterState.clusterUuid + [defaultClusterState.clusterUuid] ); expect(customLicenseService.getWatcherFeature).toHaveBeenCalled(); }); @@ -200,7 +185,7 @@ describe('fetchStatus', () => { customAlertsClient as any, licenseService as any, [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_MONITORING_DATA], - defaultClusterState.clusterUuid + [defaultClusterState.clusterUuid] ); expect(Object.keys(status)).toEqual([ ALERT_CPU_USAGE, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 399b26a6c5c314..3ccb4d3a9c4d5d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -20,7 +20,7 @@ export async function fetchStatus( alertsClient: AlertsClient, licenseService: MonitoringLicenseService, alertTypes: string[] | undefined, - clusterUuid: string, + clusterUuids: string[], filters: CommonAlertFilter[] = [] ): Promise<{ [type: string]: CommonAlertStatus }> { const types: Array<{ type: string; result: CommonAlertStatus }> = []; @@ -57,7 +57,7 @@ export async function fetchStatus( } for (const state of alertInstanceState.alertStates) { const meta = instance.meta; - if (clusterUuid && state.cluster.clusterUuid !== clusterUuid) { + if (clusterUuids && !clusterUuids.includes(state.cluster.clusterUuid)) { return accum; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index 47e3cef0674110..0bed25a70d0480 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -120,6 +120,13 @@ export async function getClustersFromRequest( // add alerts data if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { const alertsClient = req.getAlertsClient(); + const alertStatus = await fetchStatus( + alertsClient, + req.server.plugins.monitoring.info, + undefined, + clusters.map((cluster) => cluster.cluster_uuid) + ); + for (const cluster of clusters) { const verification = verifyMonitoringLicense(req.server); if (!verification.enabled) { @@ -154,12 +161,20 @@ export async function getClustersFromRequest( if (prodLicenseInfo.clusterAlerts.enabled) { try { cluster.alerts = { - list: await fetchStatus( - alertsClient, - req.server.plugins.monitoring.info, - undefined, - cluster.cluster_uuid - ), + list: Object.keys(alertStatus).reduce((accum, alertName) => { + const value = alertStatus[alertName]; + if (value.states && value.states.length) { + accum[alertName] = { + ...value, + states: value.states.filter( + (state) => state.state.cluster.clusterUuid === cluster.cluster_uuid + ), + }; + } else { + accum[alertName] = value; + } + return accum; + }, {}), alertsMeta: { enabled: true, }, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 654c3de7d81a96..4fada2d17bf5d5 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -368,7 +368,7 @@ export class MonitoringPlugin if (Boom.isBoom(err) || statusCode !== 500) { return res.customError({ statusCode, body: err }); } - return res.internalError(wrapError(err)); + throw wrapError(err).body; } }; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts index d0a4de7b5b3783..95e2cb63bec86d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts @@ -43,7 +43,7 @@ export function alertStatusRoute(server: any, npRoute: RouteDependencies) { alertsClient, npRoute.licenseService, alertTypeIds, - clusterUuid, + [clusterUuid], filters as CommonAlertFilter[] ); return response.ok({ body: status }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1n1p.json b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1n1p.json new file mode 100644 index 00000000000000..71656e3468c098 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1n1p.json @@ -0,0 +1,168 @@ +[ + { + "hits" : { + "hits": [ + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "ezNjX3cB1VO1nvgv72Wz", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "workers" : 1, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "elasticsearch", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "mutate", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "ruby", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "split", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "elasticsearch", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "ruby", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "ruby", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "mutate", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "aggregate", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "drop", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "meta" : { + "source" : { + "protocol" : "file" + } + } + }, + { + "config_name" : "mutate", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "elasticsearch", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "output" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "output" + } + ] + } + } + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T15:58:40.943-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "994e68cd-d607-40e6-a54c-02a51caa17e0" + ] + } + } + ] + } +} +] diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1nmp.json b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1nmp.json new file mode 100644 index 00000000000000..f2e3b1e038476c --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_1nmp.json @@ -0,0 +1,187 @@ +[ + { + "hits" : { + "hits" : [ + { + "_index" : ".monitoring-logstash-7-mb-2021.01.28", + "_type" : "_doc", + "_id" : "CiziSXcB1VO1nvgvRr-E", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 12 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-01-28T11:45:01.937-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "47a70feb-3cb5-4618-8670-2c0bada61acd" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.01.12", + "_type" : "_doc", + "_id" : "DSziSXcB1VO1nvgvRr-Z", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 1 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-01-28T11:45:01.955-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "5a65d966-0330-4bd7-82f2-ee81040c13cf" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.01.12", + "_type" : "_doc", + "_id" : "DCziSXcB1VO1nvgvRr-Z", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 44 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-01-28T11:45:01.937-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "8d33fe25-a2c0-4c54-9ecf-d218cb8dbfe4" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.01.12", + "_type" : "_doc", + "_id" : "CyziSXcB1VO1nvgvRr-E", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 1251, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "x-pack-config-management" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 12 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-01-28T11:45:01.937-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "f4167a94-20a8-43e7-828e-4cf38d906187" + ] + } + } + ] + } + }] diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_mnmp.json b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_mnmp.json new file mode 100644 index 00000000000000..65cb704529efd0 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_state_results_mnmp.json @@ -0,0 +1,926 @@ +[ + { + "hits" : { + "hits" : [ + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "ZzNmX3cB1VO1nvgv9Wlp", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "stdin", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "clone", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "pipeline", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 1 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:01:58.852-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "ujNxX3cB1VO1nvgvv3em", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 100, + "workers" : 1, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "pipeline", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + } + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:13:45.947-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "bc6ef6f2-ecce-4328-96a2-002de41a144d" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "ODNtX3cB1VO1nvgvo3IK", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 1, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "pipeline", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 10 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.534-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "3193df5f-2a34-4fe3-816e-6b05999aa5ce" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "LDNtX3cB1VO1nvgvonIZ", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 6, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 6 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.322-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "602936f5-98a3-4f8c-9471-cf389a519f4b" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "LTNtX3cB1VO1nvgvonIZ", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 12, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 12 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.332-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "41258219-b129-4fad-a629-f244826281f8" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "LjNtX3cB1VO1nvgvonI3", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 7, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 7 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.335-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "18593052-c021-4158-860d-d8122981a0ac" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "LzNtX3cB1VO1nvgvonI3", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 10, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 10 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.337-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "8b300988-62cc-4bc6-9ee0-9194f3f78e27" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "MDNtX3cB1VO1nvgvonJL", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 8, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 8 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.339-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "802a5994-a03c-44b8-a650-47c0f71c2e48" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "MTNtX3cB1VO1nvgvonJL", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 4, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 4 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.468-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "6ab60531-fb6f-478c-9063-82f2b0af2bed" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "MjNtX3cB1VO1nvgvonJi", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 5, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 5 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.470-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "0ec4702d-b5e5-4c60-91e9-6fa6a836f0d1" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "MzNtX3cB1VO1nvgvonJi", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 2, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "pipeline", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 2 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.489-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "ddf882b7-be26-4a93-8144-0aeb35122651" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "NDNtX3cB1VO1nvgvonJ3", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 13, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 13 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.490-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "6070b400-5c10-4c5e-b5c5-a5bd9be6d321" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "NTNtX3cB1VO1nvgvonJ3", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 3, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 3 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.512-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "e73bc63d-561a-4acd-a0c4-d5f70c4603df" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "NjNtX3cB1VO1nvgvonKM", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 11, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 11 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.513-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "4207025c-9b00-4bea-a36c-6fbf2d3c215e" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "NzNtX3cB1VO1nvgvonKM", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 9, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + }, + "workers" : 9 + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:09:16.533-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "72058ad1-68a1-45f6-a8e8-10621ffc7288" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "uDNxX3cB1VO1nvgvv3cb", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "workers" : 16, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "stdout", + "meta" : { + "source" : { + "protocol" : "file" + } + }, + "plugin_type" : "output" + } + ] + } + } + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:13:45.932-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "c6785d63-6e5f-42c2-839d-5edf139b7c19" + ] + } + }, + { + "_index" : ".monitoring-logstash-7-2021.02.01", + "_type" : "_doc", + "_id" : "uTNxX3cB1VO1nvgvv3cb", + "_score" : 0.0, + "_source" : { + "logstash_state" : { + "pipeline" : { + "batch_size" : 125, + "workers" : 8, + "representation" : { + "graph" : { + "vertices" : [ + { + "config_name" : "generator", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "input" + }, + { + "config_name" : "sleep", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "filter" + }, + { + "config_name" : "pipeline", + "meta" : { + "source" : { + "protocol" : "string" + } + }, + "plugin_type" : "output" + } + ] + } + } + } + }, + "type" : "logstash_state", + "timestamp" : "2021-02-01T16:13:45.945-0500" + }, + "fields" : { + "logstash_state.pipeline.ephemeral_id" : [ + "2fcd4161-e08f-4eea-818b-703ea3ec6389" + ] + } + } + ] + } +} +] diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_stats_results.json b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_stats_results.json new file mode 100644 index 00000000000000..f9b54f62100dc7 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/logstash_stats_results.json @@ -0,0 +1,323 @@ +[ + { + "hits" : { + "hits": [ + { + "_index": ".monitoring-logstash-7-2021.02.01", + "_type": "_doc", + "_id": "6DN5X3cB1VO1nvgvb4Ft", + "_score": null, + "_source": { + "logstash_stats": { + "logstash": { + "version": "7.10.0", + "snapshot": false, + "status": "green" + }, + "pipelines": [ + { + "id": "main", + "ephemeral_id": "cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5", + "queue": { + "max_queue_size_in_bytes": 0, + "type": "memory" + } + } + ] + }, + "cluster_uuid": "1n1p", + "type": "logstash_stats" + }, + "fields": { + "logstash_stats.logstash.uuid": [ + "12a3b26f-9b8c-4d38-b4d0-54649c1dd244" + ] + }, + "sort": [ + 1612214529897 + ] + }, + { + "_index" : ".monitoring-logstash-7-mb-2021.01.28", + "_type" : "_doc", + "_id" : "rC2jSncB1VO1nvgvsKx6", + "_score" : null, + "_source" : { + "agent" : { + "type" : "metricbeat" + }, + "logstash_stats" : { + "logstash" : { + "version" : "7.8.0", + "snapshot" : false, + "status" : "green" + }, + "pipelines" : [ + { + "id" : "test2", + "ephemeral_id" : "47a70feb-3cb5-4618-8670-2c0bada61acd", + "queue" : { + "max_queue_size_in_bytes" : 0, + "type" : "memory" + } + }, + { + "id" : "main", + "ephemeral_id" : "5a65d966-0330-4bd7-82f2-ee81040c13cf", + "queue" : { + "max_queue_size_in_bytes" : 0, + "type" : "memory" + } + }, + { + "id" : "main2", + "ephemeral_id" : "8d33fe25-a2c0-4c54-9ecf-d218cb8dbfe4", + "queue" : { + "max_queue_size_in_bytes" : 0, + "type" : "memory" + } + }, + { + "id" : "main3", + "ephemeral_id" : "f4167a94-20a8-43e7-828e-4cf38d906187", + "queue" : { + "max_queue_size_in_bytes" : 1.19185342464E11, + "type" : "persisted" + } + } + ] + }, + "cluster_uuid" : "1nmp", + "type" : "logstash_stats" + }, + "fields" : { + "logstash_stats.logstash.uuid" : [ + "1232122-f2b6-4b6c-8cea-22661f9c4134" + ] + }, + "sort" : [ + 1611864976527 + ] + }, + { + "_index": ".monitoring-logstash-7-2021.02.01", + "_type": "_doc", + "_id": "JzNyX3cB1VO1nvgvDXi5", + "_score": null, + "_source": { + "logstash_stats": { + "logstash": { + "version": "7.9.2", + "snapshot": false, + "status": "green" + }, + "pipelines": [ + { + "id": "t2", + "ephemeral_id": "2fcd4161-e08f-4eea-818b-703ea3ec6389", + "queue": { + "max_queue_size_in_bytes": 0, + "type": "memory" + } + }, + { + "id": "aggregate-test", + "ephemeral_id": "c6785d63-6e5f-42c2-839d-5edf139b7c19", + "queue": { + "max_queue_size_in_bytes": 0, + "type": "memory" + } + }, + { + "id": "t1", + "ephemeral_id": "bc6ef6f2-ecce-4328-96a2-002de41a144d", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + } + ] + }, + "cluster_uuid": "mnmp", + "type": "logstash_stats" + }, + "fields": { + "logstash_stats.logstash.uuid": [ + "701ce2f6-4e6a-426b-a959-80e7cdd608aa" + ] + }, + "sort": [ + 1612214046021 + ] + }, + { + "_index": ".monitoring-logstash-7-2021.02.01", + "_type": "_doc", + "_id": "sTNuX3cB1VO1nvgvtXN6", + "_score": null, + "_source": { + "logstash_stats": { + "logstash": { + "version": "7.9.1", + "snapshot": false, + "status": "green" + }, + "pipelines": [ + { + "id": "t9", + "ephemeral_id": "72058ad1-68a1-45f6-a8e8-10621ffc7288", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t7", + "ephemeral_id": "18593052-c021-4158-860d-d8122981a0ac", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t11", + "ephemeral_id": "4207025c-9b00-4bea-a36c-6fbf2d3c215e", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t5", + "ephemeral_id": "0ec4702d-b5e5-4c60-91e9-6fa6a836f0d1", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t12", + "ephemeral_id": "41258219-b129-4fad-a629-f244826281f8", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t3", + "ephemeral_id": "e73bc63d-561a-4acd-a0c4-d5f70c4603df", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t2", + "ephemeral_id": "ddf882b7-be26-4a93-8144-0aeb35122651", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t6", + "ephemeral_id": "602936f5-98a3-4f8c-9471-cf389a519f4b", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t10", + "ephemeral_id": "8b300988-62cc-4bc6-9ee0-9194f3f78e27", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t4", + "ephemeral_id": "6ab60531-fb6f-478c-9063-82f2b0af2bed", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t8", + "ephemeral_id": "802a5994-a03c-44b8-a650-47c0f71c2e48", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t13", + "ephemeral_id": "6070b400-5c10-4c5e-b5c5-a5bd9be6d321", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + }, + { + "id": "t1", + "ephemeral_id": "3193df5f-2a34-4fe3-816e-6b05999aa5ce", + "queue": { + "max_queue_size_in_bytes": 1073741824, + "type": "persisted" + } + } + ] + }, + "cluster_uuid": "mnmp", + "type": "logstash_stats" + }, + "fields": { + "logstash_stats.logstash.uuid": [ + "255eab6a-e15c-4e21-8d81-a9474eadb3eb" + ] + }, + "sort": [ + 1612213826805 + ] + }, + { + "_index": ".monitoring-logstash-7-2021.02.01", + "_type": "_doc", + "_id": "FDNkX3cB1VO1nvgvZWaA", + "_score": null, + "_source": { + "logstash_stats": { + "logstash": { + "version": "7.10.0", + "snapshot": false, + "status": "green" + }, + "pipelines": [ + { + "id": "main", + "ephemeral_id": "994e68cd-d607-40e6-a54c-02a51caa17e0", + "queue": { + "max_queue_size_in_bytes": 0, + "type": "memory" + } + } + ] + }, + "cluster_uuid": "mnmp", + "type": "logstash_stats" + }, + "fields": { + "logstash_stats.logstash.uuid": [ + "beb45ee3-ad5f-4895-87f0-97625a6e5295" + ] + }, + "sort": [ + 1612213150986 + ] + } + ] + } + } +] + diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts index cee95869602649..33487ecafd8c52 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts @@ -9,7 +9,7 @@ import sinon from 'sinon'; import { getStackStats, getAllStats, handleAllStats } from './get_all_stats'; import { ESClusterStats } from './get_es_stats'; import { KibanaStats } from './get_kibana_stats'; -import { ClustersHighLevelStats } from './get_high_level_stats'; +import { LogstashStatsByClusterUuid } from './get_logstash_stats'; describe('get_all_stats', () => { const timestamp = Date.now(); @@ -145,13 +145,13 @@ describe('get_all_stats', () => { logstash: { count: 1, versions: [{ version: '2.3.4-beta2', count: 1 }], - os: { - platforms: [], - platformReleases: [], - distros: [], - distroReleases: [], + cluster_stats: { + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + plugins: [], }, - cloud: undefined, }, }, }, @@ -188,7 +188,7 @@ describe('get_all_stats', () => { it('handles response', () => { const clusters = handleAllStats(esClusters as ESClusterStats[], { kibana: (kibanaStats as unknown) as KibanaStats, - logstash: (logstashStats as unknown) as ClustersHighLevelStats, + logstash: (logstashStats as unknown) as LogstashStatsByClusterUuid, beats: {}, }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index 67a21dc04fd558..60b107cb293426 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -19,7 +19,7 @@ import { import { getElasticsearchStats, ESClusterStats } from './get_es_stats'; import { getKibanaStats, KibanaStats } from './get_kibana_stats'; import { getBeatsStats, BeatsStatsByClusterUuid } from './get_beats_stats'; -import { getHighLevelStats, ClustersHighLevelStats } from './get_high_level_stats'; +import { getLogstashStats, LogstashStatsByClusterUuid } from './get_logstash_stats'; /** * Get statistics for all products joined by Elasticsearch cluster. @@ -38,7 +38,7 @@ export async function getAllStats( const [esClusters, kibana, logstash, beats] = await Promise.all([ getElasticsearchStats(callCluster, clusterUuids, maxBucketSize), // cluster_stats, stack_stats.xpack, cluster_name/uuid, license, version getKibanaStats(callCluster, clusterUuids, start, end, maxBucketSize), // stack_stats.kibana - getHighLevelStats(callCluster, clusterUuids, start, end, LOGSTASH_SYSTEM_ID, maxBucketSize), // stack_stats.logstash + getLogstashStats(callCluster, clusterUuids), // stack_stats.logstash getBeatsStats(callCluster, clusterUuids, start, end), // stack_stats.beats ]); @@ -63,7 +63,7 @@ export function handleAllStats( beats, }: { kibana: KibanaStats; - logstash: ClustersHighLevelStats; + logstash: LogstashStatsByClusterUuid; beats: BeatsStatsByClusterUuid; } ) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index f7252c9d893649..63188be142fdd6 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -53,7 +53,7 @@ type Counter = Map; * @param {Map} map Map to update the counter for the {@code key}. * @param {String} key The key to increment a counter for. */ -function incrementByKey(map: Counter, key?: string) { +export function incrementByKey(map: Counter, key?: string) { if (!key) { return; } @@ -207,7 +207,7 @@ function groupInstancesByCluster( * { [keyName]: key2, count: value2 } * ] */ -function mapToList(map: Map, keyName: string): T[] { +export function mapToList(map: Map, keyName: string): T[] { const list: T[] = []; for (const [key, count] of map) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts new file mode 100644 index 00000000000000..f2f0c37255d924 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts @@ -0,0 +1,401 @@ +/* + * 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 { + fetchLogstashStats, + fetchLogstashState, + processStatsResults, + processLogstashStateResults, +} from './get_logstash_stats'; +import sinon from 'sinon'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const logstashStatsResultSet = require('./__mocks__/fixtures/logstash_stats_results'); + +const resultsMap = new Map(); + +// Load data for state results. +['1n1p', '1nmp', 'mnmp'].forEach((data) => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + resultsMap.set(data, require(`./__mocks__/fixtures/logstash_state_results_${data}`)); +}); + +const getBaseOptions = () => ({ + clusters: {}, + allEphemeralIds: {}, + versions: {}, + plugins: {}, +}); + +describe('Get Logstash Stats', () => { + const clusterUuids = ['aCluster', 'bCluster', 'cCluster']; + let callCluster = sinon.stub(); + + beforeEach(() => { + callCluster = sinon.stub(); + }); + + describe('fetchLogstashState', () => { + const clusterUuid = 'a'; + const ephemeralIds = ['a', 'b', 'c']; + it('should create the logstash state query correctly', async () => { + const expected = { + bool: { + filter: [ + { + terms: { + 'logstash_state.pipeline.ephemeral_id': ['a', 'b', 'c'], + }, + }, + { + bool: { + must: { + term: { type: 'logstash_state' }, + }, + }, + }, + ], + }, + }; + + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + expect(api).toEqual('search'); + expect(body.query).toEqual(expected); + }); + + it('should set `from: 0, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + expect(api).toEqual('search'); + expect(body.from).toEqual(0); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 10000, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, { + page: 1, + } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(10000); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 20000, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, { + page: 2, + } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(20000); + expect(body.size).toEqual(10000); + }); + }); + + describe('fetchLogstashStats', () => { + it('should set `from: 0, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(0); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 10000, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, { page: 1 } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(10000); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 20000, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, { page: 2 } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(20000); + expect(body.size).toEqual(10000); + }); + }); + + describe('processLogstashStatsResults', () => { + it('should summarize empty results', () => { + const resultsEmpty = undefined; + + const options = getBaseOptions(); + processStatsResults(resultsEmpty as any, options); + + expect(options.clusters).toStrictEqual({}); + }); + + it('should summarize single result with some missing fields', () => { + const results = { + hits: { + hits: [ + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '61de393a-f2b6-4b6c-8cea-22661f9c4134', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + ], + }, + }; + + const options = getBaseOptions(); + processStatsResults(results as any, options); + + expect(options.clusters).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: { + count: 1, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + queues: { + memory: 1, + }, + }, + versions: [], + }, + }); + }); + + it('should summarize stats from hits across multiple result objects', () => { + const options = getBaseOptions(); + + // logstashStatsResultSet is an array of many small query results + logstashStatsResultSet.forEach((results: any) => { + processStatsResults(results, options); + }); + + resultsMap.forEach((value: string[], clusterUuid: string) => { + value.forEach((results: any) => { + processLogstashStateResults(results, clusterUuid, options); + }); + }); + + expect(options.clusters).toStrictEqual({ + '1n1p': { + count: 1, + versions: [ + { + count: 1, + version: '7.10.0', + }, + ], + cluster_stats: { + collection_types: { + internal_collection: 1, + }, + pipelines: { + batch_size_avg: 125, + batch_size_max: 125, + batch_size_min: 125, + batch_size_total: 125, + count: 1, + sources: { + file: true, + }, + workers_avg: 1, + workers_max: 1, + workers_min: 1, + workers_total: 1, + }, + plugins: [ + { + count: 1, + name: 'logstash-input-stdin', + }, + { + count: 1, + name: 'logstash-input-elasticsearch', + }, + { + count: 3, + name: 'logstash-filter-mutate', + }, + { + count: 3, + name: 'logstash-filter-ruby', + }, + { + count: 1, + name: 'logstash-filter-split', + }, + { + count: 1, + name: 'logstash-filter-elasticsearch', + }, + { + count: 1, + name: 'logstash-filter-aggregate', + }, + { + count: 1, + name: 'logstash-filter-drop', + }, + { + count: 1, + name: 'logstash-output-elasticsearch', + }, + { + count: 1, + name: 'logstash-output-stdout', + }, + ], + queues: { + memory: 1, + }, + }, + }, + '1nmp': { + count: 1, + versions: [ + { + count: 1, + version: '7.8.0', + }, + ], + cluster_stats: { + collection_types: { + metricbeat: 1, + }, + pipelines: { + batch_size_avg: 406.5, + batch_size_max: 1251, + batch_size_min: 125, + batch_size_total: 1626, + count: 4, + sources: { + xpack: true, + }, + workers_avg: 17.25, + workers_max: 44, + workers_min: 1, + workers_total: 69, + }, + plugins: [ + { + count: 4, + name: 'logstash-input-stdin', + }, + { + count: 4, + name: 'logstash-output-stdout', + }, + ], + queues: { + memory: 3, + persisted: 1, + }, + }, + }, + mnmp: { + count: 3, + versions: [ + { + count: 1, + version: '7.9.2', + }, + { + count: 1, + version: '7.9.1', + }, + { + count: 1, + version: '7.10.0', + }, + ], + cluster_stats: { + collection_types: { + internal_collection: 3, + }, + pipelines: { + batch_size_avg: 33.294117647058826, + batch_size_max: 125, + batch_size_min: 1, + batch_size_total: 566, + count: 17, + sources: { + file: true, + string: true, + }, + workers_avg: 7.411764705882353, + workers_max: 16, + workers_min: 1, + workers_total: 126, + }, + plugins: [ + { + count: 1, + name: 'logstash-input-stdin', + }, + { + count: 1, + name: 'logstash-filter-clone', + }, + { + count: 3, + name: 'logstash-output-pipeline', + }, + { + count: 2, + name: 'logstash-input-pipeline', + }, + { + count: 16, + name: 'logstash-filter-sleep', + }, + { + count: 14, + name: 'logstash-output-stdout', + }, + { + count: 14, + name: 'logstash-input-generator', + }, + ], + queues: { + memory: 3, + persisted: 14, + }, + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts new file mode 100644 index 00000000000000..93c69c644c0649 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts @@ -0,0 +1,418 @@ +/* + * 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 { SearchResponse } from 'elasticsearch'; +import { LegacyAPICaller } from 'kibana/server'; +import { createQuery } from './create_query'; +import { mapToList } from './get_high_level_stats'; +import { incrementByKey } from './get_high_level_stats'; + +import { INDEX_PATTERN_LOGSTASH, TELEMETRY_QUERY_SOURCE } from '../../common/constants'; + +type Counter = Map; + +const HITS_SIZE = 10000; // maximum hits to receive from ES with each search + +export interface LogstashBaseStats { + // stats + versions: Array<{ version: string; count: number }>; + count: number; + + cluster_stats?: { + collection_types?: { [collection_type_type: string]: number }; + queues?: { [queue_type: string]: number }; + plugins?: Array<{ name: string; count: number }>; + pipelines?: { + count?: number; + batch_size_max?: number; + batch_size_avg?: number; + batch_size_min?: number; + batch_size_total?: number; + workers_max?: number; + workers_avg?: number; + workers_min?: number; + workers_total?: number; + sources?: { [source_type: string]: boolean }; + }; + }; +} + +const getLogstashBaseStats = () => ({ + versions: [], + count: 0, + cluster_stats: { + pipelines: {}, + plugins: [], + }, +}); + +export interface LogstashStats { + cluster_uuid: string; + source_node: string; + type: string; + agent?: { + type: string; + }; + logstash_stats?: { + pipelines?: [ + { + id?: string; + ephemeral_id: string; + queue?: { + type: string; + }; + } + ]; + logstash?: { + version?: string; + uuid?: string; + snapshot?: string; + }; + }; +} + +export interface LogstashState { + logstash_state?: { + pipeline?: { + batch_size?: number; + workers?: number; + representation?: { + graph?: { + vertices?: [ + { + config_name?: string; + plugin_type?: string; + meta?: { + source?: { + protocol?: string; + }; + }; + } + ]; + }; + }; + }; + }; +} + +export interface LogstashProcessOptions { + clusters: { [clusterUuid: string]: LogstashBaseStats }; + allEphemeralIds: { [clusterUuid: string]: string[] }; + versions: { [clusterUuid: string]: Counter }; + plugins: { [clusterUuid: string]: Counter }; +} + +/* + * Update a clusters object with processed Logstash stats + * @param {Array} results - array of LogstashStats docs from ES + * @param {Object} clusters - LogstashBaseStats in an object keyed by the cluster UUIDs + * @param {Object} allEphemeralIds - EphemeralIds in an object keyed by cluster UUIDs to track the pipelines for the cluster + * @param {Object} versions - Versions in an object keyed by cluster UUIDs to track the logstash versions for the cluster + * @param {Object} plugins - plugin information keyed by cluster UUIDs to count the unique plugins + */ +export function processStatsResults( + results: SearchResponse, + { clusters, allEphemeralIds, versions, plugins }: LogstashProcessOptions +) { + const currHits = results?.hits?.hits || []; + currHits.forEach((hit) => { + const clusterUuid = hit._source.cluster_uuid; + if (clusters[clusterUuid] === undefined) { + clusters[clusterUuid] = getLogstashBaseStats(); + versions[clusterUuid] = new Map(); + plugins[clusterUuid] = new Map(); + } + const logstashStats = hit._source.logstash_stats; + const clusterStats = clusters[clusterUuid].cluster_stats; + + if (clusterStats !== undefined && logstashStats !== undefined) { + clusters[clusterUuid].count = (clusters[clusterUuid].count || 0) + 1; + + const thisVersion = logstashStats.logstash?.version; + const a: Counter = versions[clusterUuid]; + incrementByKey(a, thisVersion); + clusters[clusterUuid].versions = mapToList(a, 'version'); + + // Internal Collection has no agent field, so default to 'internal_collection' + let thisCollectionType = hit._source.agent?.type; + if (thisCollectionType === undefined) { + thisCollectionType = 'internal_collection'; + } + if (!clusterStats.hasOwnProperty('collection_types')) { + clusterStats.collection_types = {}; + } + clusterStats.collection_types![thisCollectionType] = + (clusterStats.collection_types![thisCollectionType] || 0) + 1; + + const theseEphemeralIds: string[] = []; + const pipelines = logstashStats.pipelines || []; + + pipelines.forEach((pipeline) => { + const thisQueueType = pipeline.queue?.type; + if (thisQueueType !== undefined) { + if (!clusterStats.hasOwnProperty('queues')) { + clusterStats.queues = {}; + } + clusterStats.queues![thisQueueType] = (clusterStats.queues![thisQueueType] || 0) + 1; + } + + const ephemeralId = pipeline.ephemeral_id; + if (ephemeralId !== undefined) { + theseEphemeralIds.push(ephemeralId); + } + }); + allEphemeralIds[clusterUuid] = theseEphemeralIds; + } + }); +} + +/* + * Update a clusters object with logstash state details + * @param {Array} results - array of LogstashState docs from ES + * @param {Object} clusters - LogstashBaseStats in an object keyed by the cluster UUIDs + * @param {Object} plugins - plugin information keyed by cluster UUIDs to count the unique plugins + */ +export function processLogstashStateResults( + results: SearchResponse, + clusterUuid: string, + { clusters, plugins }: LogstashProcessOptions +) { + const currHits = results?.hits?.hits || []; + const clusterStats = clusters[clusterUuid].cluster_stats; + const pipelineStats = clusters[clusterUuid].cluster_stats?.pipelines; + + currHits.forEach((hit) => { + const thisLogstashStatePipeline = hit._source.logstash_state?.pipeline; + + if (pipelineStats !== undefined && thisLogstashStatePipeline !== undefined) { + pipelineStats.count = (pipelineStats.count || 0) + 1; + + const thisPipelineBatchSize = thisLogstashStatePipeline.batch_size; + + if (thisPipelineBatchSize !== undefined) { + pipelineStats.batch_size_total = + (pipelineStats.batch_size_total || 0) + thisPipelineBatchSize; + pipelineStats.batch_size_max = pipelineStats.batch_size_max || 0; + pipelineStats.batch_size_min = pipelineStats.batch_size_min || 0; + pipelineStats.batch_size_avg = pipelineStats.batch_size_total / pipelineStats.count; + + if (thisPipelineBatchSize > pipelineStats.batch_size_max) { + pipelineStats.batch_size_max = thisPipelineBatchSize; + } + if ( + pipelineStats.batch_size_min === 0 || + thisPipelineBatchSize < pipelineStats.batch_size_min + ) { + pipelineStats.batch_size_min = thisPipelineBatchSize; + } + } + + const thisPipelineWorkers = thisLogstashStatePipeline.workers; + if (thisPipelineWorkers !== undefined) { + pipelineStats.workers_total = (pipelineStats.workers_total || 0) + thisPipelineWorkers; + pipelineStats.workers_max = pipelineStats.workers_max || 0; + pipelineStats.workers_min = pipelineStats.workers_min || 0; + pipelineStats.workers_avg = pipelineStats.workers_total / pipelineStats.count; + + if (thisPipelineWorkers > pipelineStats.workers_max) { + pipelineStats.workers_max = thisPipelineWorkers; + } + if (pipelineStats.workers_min === 0 || thisPipelineWorkers < pipelineStats.workers_min) { + pipelineStats.workers_min = thisPipelineWorkers; + } + } + + // Extract the vertices object from the pipeline representation. From this, we can + // retrieve the source of the pipeline element on the configuration(from file, string, or + // x-pack-config-management), and the input, filter and output plugins from that pipeline. + const vertices = thisLogstashStatePipeline.representation?.graph?.vertices; + + if (vertices !== undefined) { + vertices.forEach((vertex) => { + const configName = vertex.config_name; + const pluginType = vertex.plugin_type; + let pipelineConfig = vertex.meta?.source?.protocol; + + if (pipelineConfig !== undefined) { + if (pipelineConfig === 'string' || pipelineConfig === 'str') { + pipelineConfig = 'string'; + } else if (pipelineConfig === 'x-pack-config-management') { + pipelineConfig = 'xpack'; + } else { + pipelineConfig = 'file'; + } + if (!pipelineStats.hasOwnProperty('sources')) { + pipelineStats.sources = {}; + } + pipelineStats.sources![pipelineConfig] = true; + } + if (configName !== undefined && pluginType !== undefined) { + incrementByKey(plugins[clusterUuid], `logstash-${pluginType}-${configName}`); + } + }); + } + } + }); + if (clusterStats !== undefined) { + clusterStats.plugins = mapToList(plugins[clusterUuid], 'name'); + } +} + +export async function fetchLogstashStats( + callCluster: LegacyAPICaller, + clusterUuids: string[], + { page = 0, ...options }: { page?: number } & LogstashProcessOptions +): Promise { + const params = { + headers: { + 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE, + }, + index: INDEX_PATTERN_LOGSTASH, + ignoreUnavailable: true, + filterPath: [ + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.type', + 'hits.hits._source.source_node', + 'hits.hits._source.agent.type', + 'hits.hits._source.logstash_stats.pipelines.id', + 'hits.hits._source.logstash_stats.pipelines.ephemeral_id', + 'hits.hits._source.logstash_stats.pipelines.queue.type', + 'hits.hits._source.logstash_stats.logstash.version', + 'hits.hits._source.logstash_stats.logstash.uuid', + ], + body: { + query: createQuery({ + filters: [ + { terms: { cluster_uuid: clusterUuids } }, + { + bool: { + must: { term: { type: 'logstash_stats' } }, + }, + }, + ], + }), + from: page * HITS_SIZE, + collapse: { field: 'logstash_stats.logstash.uuid' }, + sort: [{ ['logstash_stats.timestamp']: { order: 'desc', unmapped_type: 'long' } }], + size: HITS_SIZE, + }, + }; + + const results = await callCluster>('search', params); + const hitsLength = results?.hits?.hits.length || 0; + + if (hitsLength > 0) { + // further augment the clusters object with more stats + processStatsResults(results, options); + + if (hitsLength === HITS_SIZE) { + // call recursively + const nextOptions = { + page: page + 1, + ...options, + }; + + // returns a promise and keeps the caller blocked from returning until the entire clusters object is built + return fetchLogstashStats(callCluster, clusterUuids, nextOptions); + } + } + return Promise.resolve(); +} + +export async function fetchLogstashState( + callCluster: LegacyAPICaller, + clusterUuid: string, + ephemeralIds: string[], + { page = 0, ...options }: { page?: number } & LogstashProcessOptions +): Promise { + const params = { + headers: { + 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE, + }, + index: INDEX_PATTERN_LOGSTASH, + ignoreUnavailable: true, + filterPath: [ + 'hits.hits._source.logstash_state.pipeline.batch_size', + 'hits.hits._source.logstash_state.pipeline.workers', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.config_name', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.plugin_type', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.meta.source.protocol', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices', + 'hits.hits._source.type', + ], + body: { + query: createQuery({ + filters: [ + { terms: { 'logstash_state.pipeline.ephemeral_id': ephemeralIds } }, + { + bool: { + must: { term: { type: 'logstash_state' } }, + }, + }, + ], + }), + from: page * HITS_SIZE, + collapse: { field: 'logstash_state.pipeline.ephemeral_id' }, + sort: [{ ['timestamp']: { order: 'desc', unmapped_type: 'long' } }], + size: HITS_SIZE, + }, + }; + + const results = await callCluster>('search', params); + const hitsLength = results?.hits?.hits.length || 0; + if (hitsLength > 0) { + // further augment the clusters object with more stats + processLogstashStateResults(results, clusterUuid, options); + + if (hitsLength === HITS_SIZE) { + // call recursively + const nextOptions = { + page: page + 1, + ...options, + }; + + // returns a promise and keeps the caller blocked from returning until the entire clusters object is built + return fetchLogstashState(callCluster, clusterUuid, ephemeralIds, nextOptions); + } + } + return Promise.resolve(); +} + +export interface LogstashStatsByClusterUuid { + [clusterUuid: string]: LogstashBaseStats; +} + +/* + * Call the function for fetching and summarizing Logstash stats + * @return {Object} - Logstash stats in an object keyed by the cluster UUIDs + */ +export async function getLogstashStats( + callCluster: LegacyAPICaller, + clusterUuids: string[] +): Promise { + const options: LogstashProcessOptions = { + clusters: {}, // the result object to be built up + allEphemeralIds: {}, + versions: {}, + plugins: {}, + }; + + await fetchLogstashStats(callCluster, clusterUuids, options); + await Promise.all( + clusterUuids.map(async (clusterUuid) => { + if (options.clusters[clusterUuid] !== undefined) { + await fetchLogstashState( + callCluster, + clusterUuid, + options.allEphemeralIds[clusterUuid], + options + ); + } + }) + ); + return options.clusters; +} diff --git a/x-pack/plugins/osquery/common/ecs/rule/index.ts b/x-pack/plugins/osquery/common/ecs/rule/index.ts index 3a764a836e9b34..51d7722a3ecdec 100644 --- a/x-pack/plugins/osquery/common/ecs/rule/index.ts +++ b/x-pack/plugins/osquery/common/ecs/rule/index.ts @@ -28,7 +28,7 @@ export interface RuleEcs { tags?: string[]; threat?: unknown; threshold?: { - field: string; + field: string | string[]; value: number; }; type?: string[]; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts index 1d9881c400ec8d..685aee16dc665c 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts @@ -88,7 +88,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; deps.router.post( diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts index 3a65bb2c54d952..89df5255d19e2e 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -95,7 +95,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; @@ -132,7 +132,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts index 25d17d796b0ee4..cfec01da943ab7 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts @@ -78,10 +78,16 @@ describe('GET remote clusters', () => { const mockContext = xpackMocks.createRequestHandlerContext(); mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; - const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + if (asserts.statusCode === 500) { + await expect(handler(mockContext, mockRequest, kibanaResponseFactory)).rejects.toThrowError( + asserts.result as Error + ); + } else { + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); - expect(response.status).toBe(asserts.statusCode); - expect(response.payload).toEqual(asserts.result); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + } if (Array.isArray(asserts.apiArguments)) { for (const apiArguments of asserts.apiArguments) { diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts index 1445316cfec37c..fbb345203e48a8 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -63,7 +63,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts index 5e1fdbb2bc0db1..99fb7dd01adb13 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts @@ -100,7 +100,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts index cd35552ed5ad71..694ab3c467c1f9 100644 --- a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts @@ -34,7 +34,7 @@ export const registerGetRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts index 854f4986e76867..90eabaa88b6410 100644 --- a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts @@ -136,7 +136,7 @@ export const registerValidateIndexPatternRoute = ({ return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts index 14ff452a4dd546..bcb3a337aa7253 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts @@ -43,7 +43,7 @@ export const registerCreateRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts index e94a1a80ce134d..4bbe73753e96cf 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts @@ -45,7 +45,7 @@ export const registerDeleteRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts index 12b3f96e778358..a9a30c0370c5f3 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts @@ -26,7 +26,7 @@ export const registerGetRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts index c560b41cc4385b..2ebfcc437f41e5 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts @@ -42,7 +42,7 @@ export const registerStartRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts index 87cf2822d4f1b6..faaf377a2d833d 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts @@ -43,7 +43,7 @@ export const registerStopRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts index 759e05dc2a3343..f77ae7829bb6c5 100644 --- a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -41,7 +41,7 @@ export const registerSearchRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 65dd15b627c7f1..a9c5fa3577476d 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -149,7 +149,6 @@ describe('AuthenticationService', () => { expect(mockAuthToolkit.authenticated).toHaveBeenCalledTimes(1); expect(mockAuthToolkit.authenticated).toHaveBeenCalledWith(); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).not.toHaveBeenCalled(); }); @@ -172,7 +171,6 @@ describe('AuthenticationService', () => { requestHeaders: mockAuthHeaders, }); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).toHaveBeenCalledTimes(1); expect(authenticate).toHaveBeenCalledWith(mockRequest); @@ -201,7 +199,6 @@ describe('AuthenticationService', () => { responseHeaders: mockAuthResponseHeaders, }); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).toHaveBeenCalledTimes(1); expect(authenticate).toHaveBeenCalledWith(mockRequest); @@ -223,7 +220,6 @@ describe('AuthenticationService', () => { 'WWW-Authenticate': 'Negotiate', }); expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); }); it('rejects with `Internal Server Error` and log error when `authenticate` throws unhandled exception', async () => { @@ -231,15 +227,12 @@ describe('AuthenticationService', () => { const failureReason = new Error('something went wrong'); authenticate.mockRejectedValue(failureReason); - await authHandler(httpServerMock.createKibanaRequest(), mockResponse, mockAuthToolkit); - - expect(mockResponse.internalError).toHaveBeenCalledTimes(1); - const [[error]] = mockResponse.internalError.mock.calls; - expect(error).toBeUndefined(); + await expect( + authHandler(httpServerMock.createKibanaRequest(), mockResponse, mockAuthToolkit) + ).rejects.toThrow(failureReason); expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith(failureReason); }); it('rejects with original `badRequest` error when `authenticate` fails to authenticate user', async () => { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 0543e2abd60df3..6848d7a3c7df63 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -93,13 +93,7 @@ export class AuthenticationService { }); } - let authenticationResult; - try { - authenticationResult = await this.authenticator.authenticate(request); - } catch (err) { - this.logger.error(err); - return response.internalError(); - } + const authenticationResult = await this.authenticator.authenticate(request); if (authenticationResult.succeeded()) { return t.authenticated({ diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index 38f832cc051ddb..654e4fc18f195a 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -413,11 +413,9 @@ describe('Common authentication routes', () => { body: { providerType: 'saml', providerName: 'saml1', currentURL: '/some-url' }, }); - await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({ - status: 500, - payload: 'Internal Error', - options: {}, - }); + await expect(routeHandler(mockContext, request, kibanaResponseFactory)).rejects.toThrow( + unhandledException + ); }); it('returns 401 if login fails.', async () => { @@ -683,11 +681,9 @@ describe('Common authentication routes', () => { authc.acknowledgeAccessAgreement.mockRejectedValue(unhandledException); const request = httpServerMock.createKibanaRequest(); - await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({ - status: 500, - payload: 'Internal Error', - options: {}, - }); + await expect(routeHandler(mockContext, request, kibanaResponseFactory)).rejects.toThrowError( + unhandledException + ); }); it('returns 204 if successfully acknowledged.', async () => { diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts index 0b0915198f3d40..f1d9aab74548a4 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.ts @@ -142,28 +142,23 @@ export function defineCommonRoutes({ logger.info(`Logging in with provider "${providerName}" (${providerType})`); const redirectURL = parseNext(currentURL, basePath.serverBasePath); - try { - const authenticationResult = await getAuthenticationService().login(request, { - provider: { name: providerName }, - redirectURL, - value: getLoginAttemptForProviderType(providerType, redirectURL, params), - }); - - if (authenticationResult.redirected() || authenticationResult.succeeded()) { - return response.ok({ - body: { location: authenticationResult.redirectURL || redirectURL }, - headers: authenticationResult.authResponseHeaders, - }); - } - - return response.unauthorized({ - body: authenticationResult.error, + const authenticationResult = await getAuthenticationService().login(request, { + provider: { name: providerName }, + redirectURL, + value: getLoginAttemptForProviderType(providerType, redirectURL, params), + }); + + if (authenticationResult.redirected() || authenticationResult.succeeded()) { + return response.ok({ + body: { location: authenticationResult.redirectURL || redirectURL }, headers: authenticationResult.authResponseHeaders, }); - } catch (err) { - logger.error(err); - return response.internalError(); } + + return response.unauthorized({ + body: authenticationResult.error, + headers: authenticationResult.authResponseHeaders, + }); }) ); @@ -178,12 +173,7 @@ export function defineCommonRoutes({ }); } - try { - await getAuthenticationService().acknowledgeAccessAgreement(request); - } catch (err) { - logger.error(err); - return response.internalError(); - } + await getAuthenticationService().acknowledgeAccessAgreement(request); return response.noContent(); }) diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index 5a08c3e1797047..73cba46f46ea70 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -76,16 +76,14 @@ describe('SAML authentication routes', () => { const unhandledException = new Error('Something went wrong.'); authc.login.mockRejectedValue(unhandledException); - const internalServerErrorResponse = Symbol('error'); const responseFactory = httpServerMock.createResponseFactory(); - responseFactory.internalError.mockReturnValue(internalServerErrorResponse as any); const request = httpServerMock.createKibanaRequest({ body: { SAMLResponse: 'saml-response' }, }); - await expect(routeHandler({} as any, request, responseFactory)).resolves.toBe( - internalServerErrorResponse + await expect(routeHandler({} as any, request, responseFactory)).rejects.toThrow( + unhandledException ); expect(authc.login).toHaveBeenCalledWith(request, { diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 9fee03cbc614a1..257b95ec707b40 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -30,28 +30,23 @@ export function defineSAMLRoutes({ options: { authRequired: false, xsrfRequired: false }, }, async (context, request, response) => { - try { - // When authenticating using SAML we _expect_ to redirect to the Kibana target location. - const authenticationResult = await getAuthenticationService().login(request, { - provider: { type: SAMLAuthenticationProvider.type }, - value: { - type: SAMLLogin.LoginWithSAMLResponse, - samlResponse: request.body.SAMLResponse, - relayState: request.body.RelayState, - }, - }); - - if (authenticationResult.redirected()) { - return response.redirected({ - headers: { location: authenticationResult.redirectURL! }, - }); - } + // When authenticating using SAML we _expect_ to redirect to the Kibana target location. + const authenticationResult = await getAuthenticationService().login(request, { + provider: { type: SAMLAuthenticationProvider.type }, + value: { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: request.body.SAMLResponse, + relayState: request.body.RelayState, + }, + }); - return response.unauthorized({ body: authenticationResult.error }); - } catch (err) { - logger.error(err); - return response.internalError(); + if (authenticationResult.redirected()) { + return response.redirected({ + headers: { location: authenticationResult.redirectURL! }, + }); } + + return response.unauthorized({ body: authenticationResult.error }); } ); } diff --git a/x-pack/plugins/security/server/routes/session_management/info.test.ts b/x-pack/plugins/security/server/routes/session_management/info.test.ts index 6ed50b50c0eb91..84db94f38d5829 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.test.ts @@ -60,11 +60,7 @@ describe('Info session routes', () => { request, kibanaResponseFactory ) - ).resolves.toEqual({ - status: 500, - options: {}, - payload: 'Internal Error', - }); + ).rejects.toThrowError(unhandledException); expect(session.get).toHaveBeenCalledWith(request); }); diff --git a/x-pack/plugins/security/server/routes/session_management/info.ts b/x-pack/plugins/security/server/routes/session_management/info.ts index f47d896eb55e00..6cab44509f162e 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.ts @@ -11,30 +11,25 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the session info. */ -export function defineSessionInfoRoutes({ router, logger, getSession }: RouteDefinitionParams) { +export function defineSessionInfoRoutes({ router, getSession }: RouteDefinitionParams) { router.get( { path: '/internal/security/session', validate: false }, async (_context, request, response) => { - try { - const sessionValue = await getSession().get(request); - if (sessionValue) { - return response.ok({ - body: { - // We can't rely on the client's system clock, so in addition to returning expiration timestamps, we also return - // the current server time -- that way the client can calculate the relative time to expiration. - now: Date.now(), - idleTimeoutExpiration: sessionValue.idleTimeoutExpiration, - lifespanExpiration: sessionValue.lifespanExpiration, - provider: sessionValue.provider, - } as SessionInfo, - }); - } - - return response.noContent(); - } catch (err) { - logger.error(`Error retrieving user session: ${err.message}`); - return response.internalError(); + const sessionValue = await getSession().get(request); + if (sessionValue) { + return response.ok({ + body: { + // We can't rely on the client's system clock, so in addition to returning expiration timestamps, we also return + // the current server time -- that way the client can calculate the relative time to expiration. + now: Date.now(), + idleTimeoutExpiration: sessionValue.idleTimeoutExpiration, + lifespanExpiration: sessionValue.lifespanExpiration, + provider: sessionValue.provider, + } as SessionInfo, + }); } + + return response.noContent(); } ); } diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index ff67e1af1e7baa..daf697bd23448b 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -46,20 +46,15 @@ export function defineAccessAgreementRoutes({ // It's not guaranteed that we'll have session for the authenticated user (e.g. when user is // authenticated with the help of HTTP authentication), that means we should safely check if // we have it and can get a corresponding configuration. - try { - const sessionValue = await getSession().get(request); - const accessAgreement = - (sessionValue && - config.authc.providers[ - sessionValue.provider.type as keyof ConfigType['authc']['providers'] - ]?.[sessionValue.provider.name]?.accessAgreement?.message) || - ''; + const sessionValue = await getSession().get(request); + const accessAgreement = + (sessionValue && + config.authc.providers[ + sessionValue.provider.type as keyof ConfigType['authc']['providers'] + ]?.[sessionValue.provider.name]?.accessAgreement?.message) || + ''; - return response.ok({ body: { accessAgreement } }); - } catch (err) { - logger.error(err); - return response.internalError(); - } + return response.ok({ body: { accessAgreement } }); }) ); } diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 34fcfa5f0befdb..31b4cef1a9d451 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -45,6 +45,11 @@ export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100; +// Document path where threat indicator fields are expected. Used as +// both the source of enrichment fields and the destination for enrichment in +// the generated detection alert +export const DEFAULT_INDICATOR_PATH = 'threat.indicator'; + export enum SecurityPageName { detections = 'detections', overview = 'overview', @@ -166,7 +171,7 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID]; /* Rule notifications options */ -export const ENABLE_CASE_CONNECTOR = false; +export const ENABLE_CASE_CONNECTOR = true; export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.email', '.slack', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index aade8be4f503fb..d97820f010a802 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -459,12 +459,21 @@ export type Threats = t.TypeOf; export const threatsOrUndefined = t.union([threats, t.undefined]); export type ThreatsOrUndefined = t.TypeOf; -export const threshold = t.exact( - t.type({ - field: t.string, - value: PositiveIntegerGreaterThanZero, - }) -); +export const threshold = t.intersection([ + t.exact( + t.type({ + field: t.union([t.string, t.array(t.string)]), + value: PositiveIntegerGreaterThanZero, + }) + ), + t.exact( + t.partial({ + cardinality_field: t.union([t.string, t.array(t.string), t.undefined, t.null]), + cardinality_value: t.union([PositiveInteger, t.undefined, t.null]), // TODO: cardinality_value should be set if cardinality_field is set + }) + ), +]); +// TODO: codec to transform threshold field string to string[] ? export type Threshold = t.TypeOf; export const thresholdOrUndefined = t.union([threshold, t.undefined]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index fb29e37a53fdbe..3bae9551f4df79 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../constants'; import { MachineLearningCreateSchema, MachineLearningUpdateSchema, @@ -56,7 +57,7 @@ export const getCreateThreatMatchRulesSchemaMock = ( rule_id: ruleId, threat_query: '*:*', threat_index: ['list-index'], - threat_indicator_path: 'threat.indicator', + threat_indicator_path: DEFAULT_INDICATOR_PATH, threat_mapping: [ { entries: [ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index cf07389e207b34..8dc7427ed09331 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; import { RulesSchema } from './rules_schema'; @@ -150,7 +151,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial> { hostName: string; + order: 'asc' | 'desc'; } + export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse { inspect?: Maybe; firstSeen?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index a430c429d54dca..fa3029405dc225 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -17,7 +17,7 @@ export * from './uncommon_processes'; export enum HostsQueries { authentications = 'authentications', details = 'details', - firstLastSeen = 'firstLastSeen', + firstOrLastSeen = 'firstOrLastSeen', hosts = 'hosts', overview = 'overviewHost', uncommonProcesses = 'uncommonProcesses', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index de88409549f8a8..319933be5c79e5 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -15,7 +15,6 @@ import { HostAuthenticationsStrategyResponse, HostOverviewRequestOptions, HostFirstLastSeenStrategyResponse, - HostFirstLastSeenRequestOptions, HostsQueries, HostsRequestOptions, HostsStrategyResponse, @@ -28,6 +27,7 @@ import { HostsKpiHostsRequestOptions, HostsKpiUniqueIpsStrategyResponse, HostsKpiUniqueIpsRequestOptions, + HostFirstLastSeenRequestOptions, } from './hosts'; import { NetworkQueries, @@ -111,7 +111,7 @@ export type StrategyResponseType = T extends HostsQ ? HostsOverviewStrategyResponse : T extends HostsQueries.authentications ? HostAuthenticationsStrategyResponse - : T extends HostsQueries.firstLastSeen + : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesStrategyResponse @@ -159,7 +159,7 @@ export type StrategyRequestType = T extends HostsQu ? HostOverviewRequestOptions : T extends HostsQueries.authentications ? HostAuthenticationsRequestOptions - : T extends HostsQueries.firstLastSeen + : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesRequestOptions diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index df6543ddbce903..c71108d58d980f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -36,7 +36,14 @@ export interface MatrixHistogramRequestOptions extends RequestBasicOptions { timerange: TimerangeInput; histogramType: MatrixHistogramType; stackByField: string; - threshold?: { field: string | undefined; value: number } | undefined; + threshold?: + | { + field: string | string[] | undefined; + value: number; + cardinality_field?: string | undefined; + cardinality_value?: number | undefined; + } + | undefined; inspect?: Maybe; isPtrIncluded?: boolean; } diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts index 5a2cf5408b04de..64ce6be9ec457b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts @@ -8,11 +8,10 @@ import { case1 } from '../../objects/case'; import { - ALL_CASES_CLOSE_ACTION, ALL_CASES_CLOSED_CASES_STATS, ALL_CASES_COMMENTS_COUNT, - ALL_CASES_DELETE_ACTION, ALL_CASES_IN_PROGRESS_CASES_STATS, + ALL_CASES_ITEM_ACTIONS_BTN, ALL_CASES_NAME, ALL_CASES_OPEN_CASES_COUNT, ALL_CASES_OPEN_CASES_STATS, @@ -91,8 +90,7 @@ describe('Cases', () => { cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0'); cy.get(ALL_CASES_OPENED_ON).should('include.text', 'ago'); cy.get(ALL_CASES_SERVICE_NOW_INCIDENT).should('have.text', 'Not pushed'); - cy.get(ALL_CASES_DELETE_ACTION).should('exist'); - cy.get(ALL_CASES_CLOSE_ACTION).should('exist'); + cy.get(ALL_CASES_ITEM_ACTIONS_BTN).should('exist'); goToCaseDetails(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index a69f808001800d..bc52be678347aa 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -98,359 +98,375 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, Indicator Match', () => { - const expectedUrls = newThreatIndicatorRule.referenceUrls.join(''); - const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join(''); - const expectedTags = newThreatIndicatorRule.tags.join(''); - const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre); - const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = 1; - - before(() => { - cleanKibana(); - esArchiverLoad('threat_indicator'); - esArchiverLoad('threat_data'); - }); - after(() => { - esArchiverUnload('threat_indicator'); - esArchiverUnload('threat_data'); - }); - - describe('Creating new indicator match rules', () => { - beforeEach(() => { - loginAndWaitForPageWithoutDateRange(RULE_CREATION); - selectIndicatorMatchType(); +// Skipped for 7.12 FF - flaky tests +describe.skip('indicator match', () => { + describe('Detection rules, Indicator Match', () => { + const expectedUrls = newThreatIndicatorRule.referenceUrls.join(''); + const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join(''); + const expectedTags = newThreatIndicatorRule.tags.join(''); + const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre); + const expectedNumberOfRules = 1; + const expectedNumberOfAlerts = 1; + + before(() => { + cleanKibana(); + esArchiverLoad('threat_indicator'); + esArchiverLoad('threat_data'); }); - - describe('Index patterns', () => { - it('Contains a predefined index pattern', () => { - getIndicatorIndex().should('have.text', indexPatterns.join('')); - }); - - it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { - getIndicatorIndicatorIndex().type(`${newThreatIndicatorRule.indicatorIndexPattern}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('not.exist'); - }); - - it('Shows invalidation text when you try to continue without filling it out', () => { - getIndexPatternClearButton().click(); - getIndicatorIndicatorIndex().type(`${newThreatIndicatorRule.indicatorIndexPattern}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); - }); + after(() => { + esArchiverUnload('threat_indicator'); + esArchiverUnload('threat_data'); }); - describe('Indicator index patterns', () => { - it('Contains empty index pattern', () => { - getIndicatorIndicatorIndex().should('have.text', ''); - }); - - it('Does NOT show invalidation text on initial page load', () => { - getIndexPatternInvalidationText().should('not.exist'); - }); - - it('Shows invalidation text if you try to continue without filling it out', () => { - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); + describe('Creating new indicator match rules', () => { + beforeEach(() => { + loginAndWaitForPageWithoutDateRange(RULE_CREATION); + selectIndicatorMatchType(); }); - }); - describe('custom query input', () => { - it('Has a default set of *:*', () => { - getCustomQueryInput().should('have.text', '*:*'); - }); + describe('Index patterns', () => { + it('Contains a predefined index pattern', () => { + getIndicatorIndex().should('have.text', indexPatterns.join('')); + }); - it('Shows invalidation text if text is removed', () => { - getCustomQueryInput().type('{selectall}{del}'); - getCustomQueryInvalidationText().should('exist'); - }); - }); + it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { + getIndicatorIndicatorIndex().type( + `${newThreatIndicatorRule.indicatorIndexPattern}{enter}` + ); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('not.exist'); + }); - describe('custom indicator query input', () => { - it('Has a default set of *:*', () => { - getCustomIndicatorQueryInput().should('have.text', '*:*'); + it('Shows invalidation text when you try to continue without filling it out', () => { + getIndexPatternClearButton().click(); + getIndicatorIndicatorIndex().type( + `${newThreatIndicatorRule.indicatorIndexPattern}{enter}` + ); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - it('Shows invalidation text if text is removed', () => { - getCustomIndicatorQueryInput().type('{selectall}{del}'); - getCustomQueryInvalidationText().should('exist'); - }); - }); + describe('Indicator index patterns', () => { + it('Contains empty index pattern', () => { + getIndicatorIndicatorIndex().should('have.text', ''); + }); - describe('Indicator mapping', () => { - beforeEach(() => { - fillIndexAndIndicatorIndexPattern( - newThreatIndicatorRule.index, - newThreatIndicatorRule.indicatorIndexPattern - ); - }); + it('Does NOT show invalidation text on initial page load', () => { + getIndexPatternInvalidationText().should('not.exist'); + }); - it('Does NOT show invalidation text on initial page load', () => { - getIndicatorInvalidationText().should('not.exist'); + it('Shows invalidation text if you try to continue without filling it out', () => { + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - it('Shows invalidation text when you try to press continue without filling anything out', () => { - getDefineContinueButton().click(); - getIndicatorAtLeastOneInvalidationText().should('exist'); - }); + describe('custom query input', () => { + it('Has a default set of *:*', () => { + getCustomQueryInput().should('have.text', '*:*'); + }); - it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { - getIndicatorAndButton().click(); - getIndicatorInvalidationText().should('exist'); + it('Shows invalidation text if text is removed', () => { + getCustomQueryInput().type('{selectall}{del}'); + getCustomQueryInvalidationText().should('exist'); + }); }); - it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { - getIndicatorOrButton().click(); - getIndicatorInvalidationText().should('exist'); - }); + describe('custom indicator query input', () => { + it('Has a default set of *:*', () => { + getCustomIndicatorQueryInput().should('have.text', '*:*'); + }); - it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Shows invalidation text if text is removed', () => { + getCustomIndicatorQueryInput().type('{selectall}{del}'); + getCustomQueryInvalidationText().should('exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('not.exist'); }); - it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + describe('Indicator mapping', () => { + beforeEach(() => { + fillIndexAndIndicatorIndexPattern( + newThreatIndicatorRule.index, + newThreatIndicatorRule.indicatorIndexPattern + ); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', + it('Does NOT show invalidation text on initial page load', () => { + getIndicatorInvalidationText().should('not.exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Shows invalidation text when you try to press continue without filling anything out', () => { + getDefineContinueButton().click(); + getIndicatorAtLeastOneInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'agent.name', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + + it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { + getIndicatorAndButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('have.text', 'agent.name'); - getIndicatorMappingComboField().should( - 'have.text', - newThreatIndicatorRule.indicatorIndexField - ); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', + it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { + getIndicatorOrButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'second-non-existent-value', - validColumns: 'indexField', + + it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorMappingComboField().should('have.text', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'second-non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + + it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('have.text', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'agent.name', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('have.text', 'agent.name'); + getIndicatorMappingComboField().should( + 'have.text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('text', 'Search'); - getIndicatorMappingComboField().should('text', 'Search'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'second-non-existent-value', + validColumns: 'indexField', + }); + getIndicatorDeleteButton().click(); + getIndicatorMappingComboField().should('have.text', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'non-existent-value', - indicatorIndexField: 'non-existent-value', - validColumns: 'none', + + it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'second-non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('have.text', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 3, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + + it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('text', 'Search'); + getIndicatorMappingComboField().should('text', 'Search'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton(2).click(); - getIndicatorIndexComboField(1).should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField(1).should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(2).should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField(2).should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(3).should('not.exist'); - getIndicatorMappingComboField(3).should('not.exist'); - }); - it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value-one', - indicatorIndexField: 'non-existent-value-two', - validColumns: 'none', + it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'non-existent-value', + indicatorIndexField: 'non-existent-value', + validColumns: 'none', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 3, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton(2).click(); + getIndicatorIndexComboField(1).should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField(1).should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField(2).should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(3).should('not.exist'); + getIndicatorMappingComboField(3).should('not.exist'); }); - getIndicatorOrButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + + it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value-one', + indicatorIndexField: 'non-existent-value-two', + validColumns: 'none', + }); + getIndicatorOrButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField().should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField().should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); }); }); - }); - describe('Generating signals', () => { - beforeEach(() => { - cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); - goToManageAlertsDetectionRules(); - waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); - goToCreateNewRule(); - selectIndicatorMatchType(); - }); + describe('Generating signals', () => { + beforeEach(() => { + cleanKibana(); + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + selectIndicatorMatchType(); + }); - it('Creates and activates a new Indicator Match rule', () => { - fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule); - fillAboutRuleAndContinue(newThreatIndicatorRule); - fillScheduleRuleAndContinue(newThreatIndicatorRule); - createAndActivateRule(); + it('Creates and activates a new Indicator Match rule', () => { + fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule); + fillAboutRuleAndContinue(newThreatIndicatorRule); + fillScheduleRuleAndContinue(newThreatIndicatorRule); + createAndActivateRule(); - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - changeToThreeHundredRowsPerPage(); - waitForRulesToBeLoaded(); + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); - }); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); - filterByCustomRules(); + filterByCustomRules(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', 1); - }); - cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name); - cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore); - cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetails(); - - cy.get(RULE_NAME_HEADER).should('have.text', `${newThreatIndicatorRule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name); + cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore); + cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('have.text', `${newThreatIndicatorRule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); + }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should( + 'have.text', + newThreatIndicatorRule.index!.join('') + ); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(INDICATOR_INDEX_PATTERNS).should( + 'have.text', + newThreatIndicatorRule.indicatorIndexPattern.join('') + ); + getDetails(INDICATOR_MAPPING).should( + 'have.text', + `${newThreatIndicatorRule.indicatorMapping} MATCHES ${newThreatIndicatorRule.indicatorIndexField}` + ); + getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should( - 'have.text', - newThreatIndicatorRule.index!.join('') - ); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(INDICATOR_INDEX_PATTERNS).should( - 'have.text', - newThreatIndicatorRule.indicatorIndexPattern.join('') - ); - getDetails(INDICATOR_MAPPING).should( - 'have.text', - `${newThreatIndicatorRule.indicatorMapping} MATCHES ${newThreatIndicatorRule.indicatorIndexField}` - ); - getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should( - 'have.text', - `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}` - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( - 'have.text', - `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}` - ); - }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should( + 'have.text', + `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}` + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( + 'have.text', + `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}` + ); + }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); - - cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); - cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name); - cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); - cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match'); - cy.get(ALERT_RULE_SEVERITY) - .first() - .should('have.text', newThreatIndicatorRule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); + cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name); + cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); + cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match'); + cy.get(ALERT_RULE_SEVERITY) + .first() + .should('have.text', newThreatIndicatorRule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index d93ed7a0e97a5c..3c188345111c85 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -80,101 +80,104 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_URL } from '../../urls/navigation'; -describe('Detection rules, threshold', () => { - const expectedUrls = newThresholdRule.referenceUrls.join(''); - const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); - const expectedTags = newThresholdRule.tags.join(''); - const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre); - - const rule = { ...newThresholdRule }; - - beforeEach(() => { - cleanKibana(); - createTimeline(newThresholdRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; +// Skipped until post-FF for 7.12 +describe.skip('Threshold Rules', () => { + describe('Detection rules, threshold', () => { + const expectedUrls = newThresholdRule.referenceUrls.join(''); + const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); + const expectedTags = newThresholdRule.tags.join(''); + const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre); + + const rule = { ...newThresholdRule }; + + beforeEach(() => { + cleanKibana(); + createTimeline(newThresholdRule.timeline).then((response) => { + rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + }); }); - }); - it('Creates and activates a new threshold rule', () => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); - goToManageAlertsDetectionRules(); - waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); - goToCreateNewRule(); - selectThresholdRuleType(); - fillDefineThresholdRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndActivateRule(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - changeToThreeHundredRowsPerPage(); - waitForRulesToBeLoaded(); - - const expectedNumberOfRules = 1; - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); - }); + it('Creates and activates a new threshold rule', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + selectThresholdRuleType(); + fillDefineThresholdRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createAndActivateRule(); + + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); + + const expectedNumberOfRules = 1; + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); - filterByCustomRules(); + filterByCustomRules(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', 1); - }); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.riskScore); - cy.get(SEVERITY).should('have.text', rule.severity); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetails(); - - cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(RISK_SCORE).should('have.text', rule.riskScore); + cy.get(SEVERITY).should('have.text', rule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); + }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(THRESHOLD_DETAILS).should( + 'have.text', + `Results aggregated by ${rule.thresholdField} >= ${rule.threshold}` + ); + }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should( + 'have.text', + `${rule.runsEvery.interval}${rule.runsEvery.type}` + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( + 'have.text', + `${rule.lookBack.interval}${rule.lookBack.type}` + ); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(THRESHOLD_DETAILS).should( - 'have.text', - `Results aggregated by ${rule.thresholdField} >= ${rule.threshold}` - ); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should( - 'have.text', - `${rule.runsEvery.interval}${rule.runsEvery.type}` - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( - 'have.text', - `${rule.lookBack.interval}${rule.lookBack.type}` - ); - }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.lt(100)); - cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); - cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); - cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); - cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.lt(100)); + cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); + cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); + cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); + cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts index 06d1a9fca91c67..e9c5ff89dd8c4b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts +++ b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts @@ -9,8 +9,6 @@ export const ALL_CASES_CASE = (id: string) => { return `[data-test-subj="cases-table-row-${id}"]`; }; -export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]'; - export const ALL_CASES_CLOSED_CASES_STATS = '[data-test-subj="closedStatsHeader"]'; export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-commentCount"]'; @@ -19,10 +17,10 @@ export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn" export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table-add-case"]'; -export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]'; - export const ALL_CASES_IN_PROGRESS_CASES_STATS = '[data-test-subj="inProgressStatsHeader"]'; +export const ALL_CASES_ITEM_ACTIONS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; + export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]'; export const ALL_CASES_OPEN_CASES_COUNT = '[data-test-subj="case-status-filter"]'; diff --git a/x-pack/plugins/security_solution/public/app/search/index.test.ts b/x-pack/plugins/security_solution/public/app/search/index.test.ts new file mode 100644 index 00000000000000..d6c36e89558d02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/search/index.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { getSearchDeepLinksAndKeywords } from '.'; +import { SecurityPageName } from '../../../common/constants'; + +describe('public search functions', () => { + it('returns a subset of links for basic license, full set for platinum', () => { + const basicLicense = 'basic'; + const platinumLicense = 'platinum'; + for (const pageName of Object.values(SecurityPageName)) { + expect.assertions(Object.values(SecurityPageName).length * 2); + const basicLinkCount = + getSearchDeepLinksAndKeywords(pageName, basicLicense).searchDeepLinks?.length || 0; + const platinumLinks = getSearchDeepLinksAndKeywords(pageName, platinumLicense); + expect(platinumLinks.searchDeepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount); + expect(platinumLinks.keywords?.length).not.toBe(null); + } + }); +}); diff --git a/x-pack/plugins/security_solution/public/app/search/index.ts b/x-pack/plugins/security_solution/public/app/search/index.ts new file mode 100644 index 00000000000000..110356269e8917 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/search/index.ts @@ -0,0 +1,242 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { Subject } from 'rxjs'; + +import { AppUpdater } from 'src/core/public'; +import { LicenseType } from '../../../../licensing/common/types'; +import { SecuritySubPluginNames, SecurityDeepLinks } from '../types'; +import { AppMeta } from '../../../../../../src/core/public'; + +const securityDeepLinks: SecurityDeepLinks = { + detections: { + base: [ + { + id: 'siemDetectionRules', + title: i18n.translate('xpack.securitySolution.search.detections.manage', { + defaultMessage: 'Manage Rules', + }), + keywords: ['rules'], + path: '/rules', + }, + ], + }, + hosts: { + base: [ + { + id: 'authentications', + title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { + defaultMessage: 'Authentications', + }), + path: '/authentications', + }, + { + id: 'uncommonProcesses', + title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { + defaultMessage: 'Uncommon Processes', + }), + path: '/uncommonProcesses', + }, + { + id: 'events', + title: i18n.translate('xpack.securitySolution.search.hosts.events', { + defaultMessage: 'Events', + }), + path: '/events', + }, + { + id: 'externalAlerts', + title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + network: { + base: [ + { + id: 'dns', + title: i18n.translate('xpack.securitySolution.search.network.dns', { + defaultMessage: 'DNS', + }), + path: '/dns', + }, + { + id: 'http', + title: i18n.translate('xpack.securitySolution.search.network.http', { + defaultMessage: 'HTTP', + }), + path: '/http', + }, + { + id: 'tls', + title: i18n.translate('xpack.securitySolution.search.network.tls', { + defaultMessage: 'TLS', + }), + path: '/tls', + }, + { + id: 'externalAlertsNetwork', + title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/external-alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + timelines: { + base: [ + { + id: 'timelineTemplates', + title: i18n.translate('xpack.securitySolution.search.timeline.templates', { + defaultMessage: 'Templates', + }), + path: '/template', + }, + ], + }, + overview: { + base: [], + }, + case: { + base: [ + { + id: 'create', + title: i18n.translate('xpack.securitySolution.search.cases.create', { + defaultMessage: 'Create New Case', + }), + path: '/create', + }, + ], + premium: [ + { + id: 'configure', + title: i18n.translate('xpack.securitySolution.search.cases.configure', { + defaultMessage: 'Configure Cases', + }), + path: '/configure', + }, + ], + }, + administration: { + base: [ + { + id: 'trustApplications', + title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', { + defaultMessage: 'Trusted Applications', + }), + path: '/trusted_apps', + }, + ], + }, +}; + +const subpluginKeywords: { [key in SecuritySubPluginNames]: string[] } = { + detections: [ + i18n.translate('xpack.securitySolution.search.detections', { + defaultMessage: 'Detections', + }), + ], + hosts: [ + i18n.translate('xpack.securitySolution.search.hosts', { + defaultMessage: 'Hosts', + }), + ], + network: [ + i18n.translate('xpack.securitySolution.search.network', { + defaultMessage: 'Network', + }), + ], + timelines: [ + i18n.translate('xpack.securitySolution.search.timelines', { + defaultMessage: 'Timelines', + }), + ], + overview: [ + i18n.translate('xpack.securitySolution.search.overview', { + defaultMessage: 'Overview', + }), + ], + case: [ + i18n.translate('xpack.securitySolution.search.cases', { + defaultMessage: 'Cases', + }), + ], + administration: [ + i18n.translate('xpack.securitySolution.search.administration', { + defaultMessage: 'Endpoint Administration', + }), + ], +}; + +/** + * A function that generates a subPlugin's meta tag + * @param subPluginName SubPluginName of the app to retrieve meta information for. + * @param licenseType optional string for license level, if not provided basic is assumed. + */ +export function getSearchDeepLinksAndKeywords( + subPluginName: SecuritySubPluginNames, + licenseType?: LicenseType +): AppMeta { + const baseRoutes = [...securityDeepLinks[subPluginName].base]; + if ( + licenseType === 'gold' || + licenseType === 'platinum' || + licenseType === 'enterprise' || + licenseType === 'trial' + ) { + const premiumRoutes = + securityDeepLinks[subPluginName] && securityDeepLinks[subPluginName].premium; + if (premiumRoutes !== undefined) { + return { + keywords: subpluginKeywords[subPluginName], + searchDeepLinks: [...baseRoutes, ...premiumRoutes], + }; + } + } + return { + keywords: subpluginKeywords[subPluginName], + searchDeepLinks: baseRoutes, + }; +} +/** + * A function that updates a subplugin's meta property as appropriate when license level changes. + * @param subPluginName SubPluginName of the app to register searchDeepLinks for + * @param appUpdater an instance of appUpdater$ observable to update search links when needed. + * @param licenseType A string representing the current license level. + */ +export function registerSearchLinks( + subPluginName: SecuritySubPluginNames, + appUpdater?: Subject, + licenseType?: LicenseType +) { + if (appUpdater !== undefined) { + appUpdater.next(() => ({ + meta: getSearchDeepLinksAndKeywords(subPluginName, licenseType), + })); + } +} diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 4d1c091fdaa5fd..95e64fe37d3331 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -17,7 +17,7 @@ import { CombinedState, } from 'redux'; -import { AppMountParameters } from '../../../../../src/core/public'; +import { AppMountParameters, AppSearchDeepLink } from '../../../../../src/core/public'; import { StartServices } from '../types'; import { AppFrontendLibs } from '../common/lib/lib'; @@ -34,6 +34,7 @@ import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; import { TimelineState } from '../timelines/store/timeline/types'; +import { SecurityPageName } from '../../common/constants'; export { SecurityPageName } from '../../common/constants'; export interface SecuritySubPluginStore { @@ -55,6 +56,15 @@ export type SecuritySubPluginKeyStore = | 'alertList' | 'management'; +export type SecuritySubPluginNames = keyof typeof SecurityPageName; + +interface SecurityDeepLink { + base: AppSearchDeepLink[]; + premium?: AppSearchDeepLink[]; +} + +export type SecurityDeepLinks = { [key in SecuritySubPluginNames]: SecurityDeepLink }; + /** * Returned by the various 'SecuritySubPlugin' classes from the `start` method. */ diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index c447e00cbb94fc..d02f7e0ee09615 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -79,7 +79,12 @@ describe('AddComment ', () => { await waitFor(() => { expect(onCommentSaving).toBeCalled(); - expect(postComment).toBeCalledWith(addCommentProps.caseId, sampleData, onCommentPosted); + expect(postComment).toBeCalledWith({ + caseId: addCommentProps.caseId, + data: sampleData, + subCaseId: undefined, + updateCase: onCommentPosted, + }); expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(''); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index 01b86a989e022f..c94ef75523e2cc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -39,11 +39,15 @@ interface AddCommentProps { onCommentSaving?: () => void; onCommentPosted: (newCase: Case) => void; showLoading?: boolean; + subCaseId?: string; } export const AddComment = React.memo( forwardRef( - ({ caseId, disabled, showLoading = true, onCommentPosted, onCommentSaving }, ref) => { + ( + { caseId, disabled, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, + ref + ) => { const { isLoading, postComment } = usePostComment(); const { form } = useForm({ @@ -80,10 +84,15 @@ export const AddComment = React.memo( if (onCommentSaving != null) { onCommentSaving(); } - postComment(caseId, { ...data, type: CommentType.user }, onCommentPosted); + postComment({ + caseId, + data: { ...data, type: CommentType.user }, + updateCase: onCommentPosted, + subCaseId, + }); reset(); } - }, [onCommentPosted, onCommentSaving, postComment, reset, submit, caseId]); + }, [caseId, onCommentPosted, onCommentSaving, postComment, reset, submit, subCaseId]); return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx index ae2d9879559931..66563deae54227 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx @@ -9,8 +9,9 @@ import { Dispatch } from 'react'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; import { CaseStatuses } from '../../../../../case/common/api'; -import { Case } from '../../containers/types'; +import { Case, SubCase } from '../../containers/types'; import { UpdateCase } from '../../containers/use_get_cases'; +import { statuses } from '../status'; import * as i18n from './translations'; interface GetActions { @@ -19,46 +20,74 @@ interface GetActions { deleteCaseOnClick: (deleteCase: Case) => void; } +const hasSubCases = (subCases: SubCase[] | null | undefined) => + subCases != null && subCases?.length > 0; + export const getActions = ({ caseStatus, dispatchUpdate, deleteCaseOnClick, -}: GetActions): Array> => [ - { - description: i18n.DELETE_CASE, - icon: 'trash', - name: i18n.DELETE_CASE, - onClick: deleteCaseOnClick, - type: 'icon', - 'data-test-subj': 'action-delete', - }, - caseStatus === CaseStatuses.open - ? { - description: i18n.CLOSE_CASE, - icon: 'folderCheck', - name: i18n.CLOSE_CASE, - onClick: (theCase: Case) => - dispatchUpdate({ - updateKey: 'status', - updateValue: CaseStatuses.closed, - caseId: theCase.id, - version: theCase.version, - }), - type: 'icon', - 'data-test-subj': 'action-close', - } - : { - description: i18n.REOPEN_CASE, - icon: 'folderExclamation', - name: i18n.REOPEN_CASE, - onClick: (theCase: Case) => - dispatchUpdate({ - updateKey: 'status', - updateValue: CaseStatuses.open, - caseId: theCase.id, - version: theCase.version, - }), - type: 'icon', - 'data-test-subj': 'action-open', - }, -]; +}: GetActions): Array> => { + const openCaseAction = { + available: (item: Case) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases), + description: statuses[CaseStatuses.open].actions.single.title, + icon: statuses[CaseStatuses.open].icon, + name: statuses[CaseStatuses.open].actions.single.title, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses.open, + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon' as const, + 'data-test-subj': 'action-open', + }; + + const makeInProgressAction = { + available: (item: Case) => + caseStatus !== CaseStatuses['in-progress'] && !hasSubCases(item.subCases), + description: statuses[CaseStatuses['in-progress']].actions.single.title, + icon: statuses[CaseStatuses['in-progress']].icon, + name: statuses[CaseStatuses['in-progress']].actions.single.title, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses['in-progress'], + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon' as const, + 'data-test-subj': 'action-in-progress', + }; + + const closeCaseAction = { + available: (item: Case) => caseStatus !== CaseStatuses.closed && !hasSubCases(item.subCases), + description: statuses[CaseStatuses.closed].actions.single.title, + icon: statuses[CaseStatuses.closed].icon, + name: statuses[CaseStatuses.closed].actions.single.title, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses.closed, + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon' as const, + 'data-test-subj': 'action-close', + }; + + return [ + { + description: i18n.DELETE_CASE, + icon: 'trash', + name: i18n.DELETE_CASE, + onClick: deleteCaseOnClick, + type: 'icon', + 'data-test-subj': 'action-delete', + }, + openCaseAction, + makeInProgressAction, + closeCaseAction, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx index 38f1b343670c8e..47db362c7b4bfe 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx @@ -14,17 +14,20 @@ import { EuiTableActionsColumnType, EuiTableComputedColumnType, EuiTableFieldDataColumnType, - HorizontalAlignment, } from '@elastic/eui'; +import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; import { CaseStatuses } from '../../../../../case/common/api'; import { getEmptyTagValue } from '../../../common/components/empty_value'; -import { Case } from '../../containers/types'; +import { Case, SubCase } from '../../containers/types'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; import { CaseDetailsLink } from '../../../common/components/links'; import * as i18n from './translations'; +import { Status } from '../status'; +import { getSubCasesStatusCountsBadges, isSubCase } from './helpers'; +import { ALERTS } from '../../../app/home/translations'; export type CasesColumns = | EuiTableFieldDataColumnType @@ -54,10 +57,14 @@ export const getCasesColumns = ( const columns = [ { name: i18n.NAME, - render: (theCase: Case) => { + render: (theCase: Case | SubCase) => { if (theCase.id != null && theCase.title != null) { const caseDetailsLinkComponent = !isModal ? ( - + {theCase.title} ) : ( @@ -122,7 +129,17 @@ export const getCasesColumns = ( truncateText: true, }, { - align: 'right' as HorizontalAlignment, + align: RIGHT_ALIGNMENT, + field: 'totalAlerts', + name: ALERTS, + sortable: true, + render: (totalAlerts: Case['totalAlerts']) => + totalAlerts != null + ? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`) + : getEmptyTagValue(), + }, + { + align: RIGHT_ALIGNMENT, field: 'totalComment', name: i18n.COMMENTS, sortable: true, @@ -183,6 +200,24 @@ export const getCasesColumns = ( return getEmptyTagValue(); }, }, + { + name: i18n.STATUS, + render: (theCase: Case) => { + if (theCase?.subCases == null || theCase.subCases.length === 0) { + if (theCase.status == null) { + return getEmptyTagValue(); + } + return ; + } + + const badges = getSubCasesStatusCountsBadges(theCase.subCases); + return badges.map(({ color, count }, index) => ( + + {count} + + )); + }, + }, { name: i18n.ACTIONS, actions, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx new file mode 100644 index 00000000000000..bb4bd0f98949da --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx @@ -0,0 +1,68 @@ +/* + * 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 React from 'react'; +import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; +import styled from 'styled-components'; +import { Case } from '../../containers/types'; +import { CasesColumns } from './columns'; +import { AssociationType } from '../../../../../case/common/api'; + +type ExpandedRowMap = Record | {}; + +const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any +const BasicTable = styled(EuiBasicTable)` + thead { + display: none; + } + + tbody { + .euiTableCellContent { + padding: 8px !important; + } + .euiTableRowCell { + border: 0; + } + } +`; +BasicTable.displayName = 'BasicTable'; + +export const getExpandedRowMap = ({ + data, + columns, +}: { + data: Case[] | null; + columns: CasesColumns[]; +}): ExpandedRowMap => { + if (data == null) { + return {}; + } + + return data.reduce((acc, curr) => { + if (curr.subCases != null) { + const subCases = curr.subCases.map((subCase, index) => ({ + ...subCase, + caseParentId: curr.id, + title: `${curr.title} ${index + 1}`, + associationType: AssociationType.subCase, + })); + return { + ...acc, + [curr.id]: ( + + ), + }; + } else { + return acc; + } + }, {}); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts new file mode 100644 index 00000000000000..1ab36d3c672250 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts @@ -0,0 +1,37 @@ +/* + * 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 { filter } from 'lodash/fp'; +import { AssociationType, CaseStatuses } from '../../../../../case/common/api'; +import { Case, SubCase } from '../../containers/types'; +import { statuses } from '../status'; + +export const isSubCase = (theCase: Case | SubCase): theCase is SubCase => + (theCase as SubCase).caseParentId !== undefined && + (theCase as SubCase).associationType === AssociationType.subCase; + +export const getSubCasesStatusCountsBadges = ( + subCases: SubCase[] +): Array<{ name: CaseStatuses; color: string; count: number }> => { + return [ + { + name: CaseStatuses.open, + color: statuses[CaseStatuses.open].color, + count: filter({ status: CaseStatuses.open }, subCases).length, + }, + { + name: CaseStatuses['in-progress'], + color: statuses[CaseStatuses['in-progress']].color, + count: filter({ status: CaseStatuses['in-progress'] }, subCases).length, + }, + { + name: CaseStatuses.closed, + color: statuses[CaseStatuses.closed].color, + count: filter({ status: CaseStatuses.closed }, subCases).length, + }, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index 318143426af584..a145bdf117813e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -210,9 +210,12 @@ describe('AllCases', () => { id: null, createdAt: null, createdBy: null, + status: null, + subCases: null, tags: null, title: null, totalComment: null, + totalAlerts: null, }, ], }, @@ -278,6 +281,7 @@ describe('AllCases', () => { ); await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); wrapper.find('[data-test-subj="action-close"]').first().simulate('click'); const firstCase = useGetCasesMockState.data.cases[0]; expect(dispatchUpdateCaseProperty).toBeCalledWith({ @@ -302,6 +306,7 @@ describe('AllCases', () => { ); await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); wrapper.find('[data-test-subj="action-open"]').first().simulate('click'); const firstCase = useGetCasesMockState.data.cases[0]; expect(dispatchUpdateCaseProperty).toBeCalledWith({ @@ -314,6 +319,26 @@ describe('AllCases', () => { }); }); + it('put case in progress when row action icon clicked', async () => { + const wrapper = mount( + + + + ); + await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); + wrapper.find('[data-test-subj="action-in-progress"]').first().simulate('click'); + const firstCase = useGetCasesMockState.data.cases[0]; + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: CaseStatuses['in-progress'], + refetchCasesStatus: fetchCasesStatus, + version: firstCase.version, + }); + }); + }); + it('Bulk delete', async () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, @@ -392,6 +417,27 @@ describe('AllCases', () => { }); }); + it('Bulk in-progress status update', async () => { + useGetCasesMock.mockReturnValue({ + ...defaultGetCases, + selectedCases: useGetCasesMockState.data.cases, + }); + + const wrapper = mount( + + + + ); + await waitFor(() => { + wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); + wrapper.find('[data-test-subj="cases-bulk-in-progress-button"]').first().simulate('click'); + expect(updateBulkStatus).toBeCalledWith( + useGetCasesMockState.data.cases, + CaseStatuses['in-progress'] + ); + }); + }); + it('isDeleted is true, refetch', async () => { useDeleteCasesMock.mockReturnValue({ ...defaultDeleteCases, @@ -542,6 +588,7 @@ describe('AllCases', () => { }, id: '1', status: 'open', + subCaseIds: [], tags: ['coke', 'pepsi'], title: 'Another horrible breach!!', totalAlerts: 0, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 001251a8a71ae6..ce0fea07bf473c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { - EuiBasicTable, + EuiBasicTable as _EuiBasicTable, EuiContextMenuPanel, EuiEmptyPrompt, EuiFlexGroup, @@ -53,6 +53,7 @@ import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; import { APP_ID } from '../../../../common/constants'; import { Stats } from '../status'; +import { getExpandedRowMap } from './expanded_row'; const Div = styled.div` margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; @@ -83,6 +84,14 @@ const getSortField = (field: string): SortFieldCase => { return SortFieldCase.createdAt; }; +const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any +const BasicTable = styled(EuiBasicTable)` + .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent { + padding: 8px 0 8px 32px; + } +`; +BasicTable.displayName = 'BasicTable'; + interface AllCasesProps { onRowClick?: (theCase?: Case) => void; isModal?: boolean; @@ -130,7 +139,7 @@ export const AllCases = React.memo( isUpdated, updateBulkStatus, } = useUpdateCases(); - const [deleteThisCase, setDeleteThisCase] = useState({ + const [deleteThisCase, setDeleteThisCase] = useState({ title: '', id: '', }); @@ -190,7 +199,7 @@ export const AllCases = React.memo( const toggleDeleteModal = useCallback( (deleteCase: Case) => { handleToggleModal(); - setDeleteThisCase(deleteCase); + setDeleteThisCase({ id: deleteCase.id, title: deleteCase.title, type: deleteCase.type }); }, [handleToggleModal] ); @@ -201,7 +210,11 @@ export const AllCases = React.memo( if (caseIds.length === 1) { const singleCase = selectedCases.find((theCase) => theCase.id === caseIds[0]); if (singleCase) { - return setDeleteThisCase({ id: singleCase.id, title: singleCase.title }); + return setDeleteThisCase({ + id: singleCase.id, + title: singleCase.title, + type: singleCase.type, + }); } } const convertToDeleteCases: DeleteCase[] = caseIds.map((id) => ({ id })); @@ -315,6 +328,16 @@ export const AllCases = React.memo( () => getCasesColumns(userCanCrud ? actions : [], filterOptions.status, isModal), [actions, filterOptions.status, userCanCrud, isModal] ); + + const itemIdToExpandedRowMap = useMemo( + () => + getExpandedRowMap({ + columns: memoizedGetCasesColumns, + data: data.cases, + }), + [data.cases, memoizedGetCasesColumns] + ); + const memoizedPagination = useMemo( () => ({ pageIndex: queryParams.page - 1, @@ -330,7 +353,10 @@ export const AllCases = React.memo( }; const euiBasicTableSelectionProps = useMemo>( - () => ({ onSelectionChange: setSelectedCases }), + () => ({ + selectable: (theCase) => isEmpty(theCase.subCases), + onSelectionChange: setSelectedCases, + }), [setSelectedCases] ); const isCasesLoading = useMemo( @@ -472,12 +498,13 @@ export const AllCases = React.memo( )} - {i18n.NO_CASES}

} diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts index bcc5df6c39e5bf..3b27ef25eda146 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts @@ -103,3 +103,7 @@ export const SERVICENOW_LINK_ARIA = i18n.translate( defaultMessage: 'click to view the incident on servicenow', } ); + +export const STATUS = i18n.translate('xpack.securitySolution.case.caseTable.status', { + defaultMessage: 'Status', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx index f9722b3903b122..ec3b391cdcbfee 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx @@ -9,10 +9,11 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; import { CaseStatuses } from '../../../../../case/common/api'; +import { statuses } from '../status'; import * as i18n from './translations'; interface GetBulkItems { - caseStatus: string; + caseStatus: CaseStatuses; closePopover: () => void; deleteCasesAction: (cases: string[]) => void; selectedCaseIds: string[]; @@ -26,34 +27,72 @@ export const getBulkItems = ({ selectedCaseIds, updateCaseStatus, }: GetBulkItems) => { + let statusMenuItems: JSX.Element[] = []; + + const openMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses.open); + }} + > + {statuses[CaseStatuses.open].actions.bulk.title} + + ); + + const inProgressMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses['in-progress']); + }} + > + {statuses[CaseStatuses['in-progress']].actions.bulk.title} + + ); + + const closeMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses.closed); + }} + > + {statuses[CaseStatuses.closed].actions.bulk.title} + + ); + + switch (caseStatus) { + case CaseStatuses.open: + statusMenuItems = [inProgressMenuItem, closeMenuItem]; + break; + + case CaseStatuses['in-progress']: + statusMenuItems = [openMenuItem, closeMenuItem]; + break; + + case CaseStatuses.closed: + statusMenuItems = [openMenuItem, inProgressMenuItem]; + break; + + default: + break; + } + return [ - caseStatus === CaseStatuses.open ? ( - { - closePopover(); - updateCaseStatus(CaseStatuses.closed); - }} - > - {i18n.BULK_ACTION_CLOSE_SELECTED} - - ) : ( - { - closePopover(); - updateCaseStatus(CaseStatuses.open); - }} - > - {i18n.BULK_ACTION_OPEN_SELECTED} - - ), + ...statusMenuItems, { - describe('getRuleIdsFromComments', () => { - it('it returns the rules ids from the comments', () => { - expect(getRuleIdsFromComments(comments)).toEqual(['alert-id-1', 'alert-id-2']); + describe('getAlertIdsFromComments', () => { + it('it returns the alert id from the comments where rule is not defined', () => { + expect(getManualAlertIdsWithNoRuleId(comments)).toEqual(['alert-id-1']); }); }); @@ -54,13 +62,13 @@ describe('Case view helpers', () => { query: { bool: { filter: { - bool: { - should: [{ match: { _id: 'alert-id-1' } }, { match: { _id: 'alert-id-2' } }], - minimum_should_match: 1, + ids: { + values: ['alert-id-1', 'alert-id-2'], }, }, }, }, + size: 10000, }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts index 6b92e414675e2c..3dece29e64ac5e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -5,28 +5,37 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { CommentType } from '../../../../../case/common/api'; import { Comment } from '../../containers/types'; -export const getRuleIdsFromComments = (comments: Comment[]) => - comments.reduce((ruleIds, comment: Comment) => { - if (comment.type === CommentType.alert) { +export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { + const dedupeAlerts = comments.reduce((alertIds, comment: Comment) => { + if (comment.type === CommentType.alert && isEmpty(comment.rule.id)) { const ids = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; - return [...ruleIds, ...ids]; + ids.forEach((id) => alertIds.add(id)); + return alertIds; } + return alertIds; + }, new Set()); + return [...dedupeAlerts]; +}; - return ruleIds; - }, []); - -export const buildAlertsQuery = (ruleIds: string[]) => ({ - query: { - bool: { - filter: { - bool: { - should: ruleIds.map((_id) => ({ match: { _id } })), - minimum_should_match: 1, +// TODO we need to allow -> docValueFields: [{ field: "@timestamp" }], +export const buildAlertsQuery = (alertIds: string[]) => { + if (alertIds.length === 0) { + return {}; + } + return { + query: { + bool: { + filter: { + ids: { + values: alertIds, + }, }, }, }, - }, -}); + size: 10000, + }; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index dc0ef9ad026a41..7a5f6647a8dcf6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -629,6 +629,14 @@ describe('CaseView ', () => { loading: true, data: { hits: { hits: [] } }, })); + useGetCaseUserActionsMock.mockReturnValue({ + caseServices: {}, + caseUserActions: [], + hasDataToPush: false, + isError: false, + isLoading: true, + participants: [], + }); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 0eaa867077a4a7..e42431e55ee290 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -42,8 +42,6 @@ import { normalizeActionConnector, getNoneConnector, } from '../configure_cases/utils'; -import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; -import { buildAlertsQuery, getRuleIdsFromComments } from './helpers'; import { DetailsPanel } from '../../../timelines/components/side_panel'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -55,6 +53,7 @@ import * as i18n from './translations'; interface Props { caseId: string; + subCaseId?: string; userCanCrud: boolean; } @@ -87,32 +86,8 @@ export interface CaseProps extends Props { updateCase: (newCase: Case) => void; } -interface Signal { - rule: { - id: string; - name: string; - to: string; - from: string; - }; -} - -interface SignalHit { - _id: string; - _index: string; - _source: { - '@timestamp': string; - signal: Signal; - }; -} - -export type Alert = { - _id: string; - _index: string; - '@timestamp': string; -} & Signal; - export const CaseComponent = React.memo( - ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { + ({ caseId, caseData, fetchCase, subCaseId, updateCase, userCanCrud }) => { const dispatch = useDispatch(); const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const allCasesLink = getCaseUrl(search); @@ -127,45 +102,18 @@ export const CaseComponent = React.memo( hasDataToPush, isLoading: isLoadingUserActions, participants, - } = useGetCaseUserActions(caseId, caseData.connector.id); + } = useGetCaseUserActions(caseId, caseData.connector.id, subCaseId); const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ caseId, + subCaseId, }); - const alertsQuery = useMemo(() => buildAlertsQuery(getRuleIdsFromComments(caseData.comments)), [ - caseData.comments, - ]); - /** * For the future developer: useSourcererScope is security solution dependent. * You can use useSignalIndex as an alternative. */ - const { browserFields, docValueFields, selectedPatterns } = useSourcererScope( - SourcererScopeName.detections - ); - - const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts( - alertsQuery, - selectedPatterns[0] - ); - - const alerts = useMemo( - () => - alertsData?.hits.hits.reduce>( - (acc, { _id, _index, _source }) => ({ - ...acc, - [_id]: { - _id, - _index, - '@timestamp': _source['@timestamp'], - ..._source.signal, - }, - }), - {} - ) ?? {}, - [alertsData?.hits.hits] - ); + const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.detections); // Update Fields const onUpdateField = useCallback( @@ -350,10 +298,10 @@ export const CaseComponent = React.memo( ); useEffect(() => { - if (initLoadingData && !isLoadingUserActions && !isLoadingAlerts) { + if (initLoadingData && !isLoadingUserActions) { setInitLoadingData(false); } - }, [initLoadingData, isLoadingAlerts, isLoadingUserActions]); + }, [initLoadingData, isLoadingUserActions]); const backOptions = useMemo( () => ({ @@ -435,18 +383,17 @@ export const CaseComponent = React.memo( {!initLoadingData && ( <> @@ -513,8 +460,8 @@ export const CaseComponent = React.memo( } ); -export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { - const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId); +export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) => { + const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId, subCaseId); if (isError) { return null; } @@ -531,6 +478,7 @@ export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { return ( void; @@ -36,7 +36,13 @@ const ConfirmDeleteCaseModalComp: React.FC = ({ defaultFocusedButton="confirm" onCancel={onCancel} onConfirm={onConfirm} - title={isPlural ? i18n.DELETE_SELECTED_CASES : i18n.DELETE_TITLE(caseTitle)} + title={ + isPlural + ? i18n.DELETE_SELECTED_CASES + : caseTitle == null + ? i18n.DELETE_THIS_CASE + : i18n.DELETE_TITLE(caseTitle) + } > {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION} diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts index 2c2ebc53607994..0bd37fa18281a6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts @@ -14,6 +14,11 @@ export const DELETE_TITLE = (caseTitle: string) => defaultMessage: 'Delete "{caseTitle}"', }); +export const DELETE_THIS_CASE = (caseTitle: string) => + i18n.translate('xpack.securitySolution.case.confirmDeleteCase.deleteThisCase', { + defaultMessage: 'Delete this case', + }); + export const CONFIRM_QUESTION = i18n.translate( 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestion', { diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx index 656257f2b36c43..d5c90bd09a6db2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx @@ -27,9 +27,7 @@ const Container = styled.div` const defaultAlertComment = { type: CommentType.generatedAlert, - alerts: '{{context.alerts}}', - index: '{{context.rule.output_index}}', - ruleId: '{{context.rule.id}}', + alerts: `[{{#context.alerts}}{"_id": "{{_id}}", "_index": "{{_index}}", "ruleId": "{{rule.id}}", "ruleName": "{{rule.name}}"}__SEPARATOR__{{/context.alerts}}]`, }; const CaseParamsFields: React.FunctionComponent> = ({ diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx index 348e488a36a8d6..1c786bade97533 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx @@ -5,10 +5,22 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; -import { useGetCases } from '../../../containers/use_get_cases'; +import { + EuiButton, + EuiButtonIcon, + EuiCallOut, + EuiTextColor, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import React, { memo, useEffect, useCallback, useState } from 'react'; +import { CaseType } from '../../../../../../case/common/api'; +import { Case } from '../../../containers/types'; +import { useDeleteCases } from '../../../containers/use_delete_cases'; +import { useGetCase } from '../../../containers/use_get_case'; +import { ConfirmDeleteCaseModal } from '../../confirm_delete_case'; import { useCreateCaseModal } from '../../use_create_case_modal'; -import { CasesDropdown, ADD_CASE_BUTTON_ID } from './cases_dropdown'; +import * as i18n from './translations'; interface ExistingCaseProps { selectedCase: string | null; @@ -16,37 +28,72 @@ interface ExistingCaseProps { } const ExistingCaseComponent: React.FC = ({ onCaseChanged, selectedCase }) => { - const { data: cases, loading: isLoadingCases, refetchCases } = useGetCases(); + const { data, isLoading, isError } = useGetCase(selectedCase ?? ''); + const [createdCase, setCreatedCase] = useState(null); - const onCaseCreated = useCallback(() => refetchCases(), [refetchCases]); + const onCaseCreated = useCallback( + (newCase: Case) => { + onCaseChanged(newCase.id); + setCreatedCase(newCase); + }, + [onCaseChanged] + ); - const { modal, openModal } = useCreateCaseModal({ onCaseCreated }); + const { modal, openModal } = useCreateCaseModal({ caseType: CaseType.collection, onCaseCreated }); - const onChange = useCallback( - (id: string) => { - if (id === ADD_CASE_BUTTON_ID) { - openModal(); - return; - } + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isLoading: isDeleting, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); - onCaseChanged(id); - }, - [onCaseChanged, openModal] - ); + useEffect(() => { + if (isDeleted) { + setCreatedCase(null); + onCaseChanged(''); + dispatchResetIsDeleted(); + } + }, [isDeleted, dispatchResetIsDeleted, onCaseChanged]); - const isCasesLoading = useMemo( - () => isLoadingCases.includes('cases') || isLoadingCases.includes('caseUpdate'), - [isLoadingCases] - ); + useEffect(() => { + if (!isLoading && !isError && data != null) { + setCreatedCase(data); + onCaseChanged(data.id); + } + }, [data, isLoading, isError, onCaseChanged]); return ( <> - + {createdCase == null && isEmpty(selectedCase) && ( + + {i18n.CREATE_CASE} + + )} + {createdCase == null && isLoading && } + {createdCase != null && !isLoading && ( + <> + + + {createdCase.title}{' '} + {!isDeleting && ( + + )} + {isDeleting && } + + + + + )} {modal} ); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts index c9553455f687da..731e94a17d9235 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts @@ -85,3 +85,17 @@ export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( defaultMessage: 'Add new case', } ); + +export const CREATE_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.createCaseLabel', + { + defaultMessage: 'Create case', + } +); + +export const CONNECTED_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.connectedCaseLabel', + { + defaultMessage: 'Connected case', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx new file mode 100644 index 00000000000000..842fe9e00ab390 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx @@ -0,0 +1,115 @@ +/* + * 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. + */ + +/* eslint-disable react/display-name */ +import React, { ReactNode } from 'react'; +import { mount } from 'enzyme'; + +import '../../../common/mock/match_media'; +import { CreateCaseFlyout } from './flyout'; +import { TestProviders } from '../../../common/mock'; + +jest.mock('../create/form_context', () => { + return { + FormContext: ({ + children, + onSuccess, + }: { + children: ReactNode; + onSuccess: ({ id }: { id: string }) => void; + }) => { + return ( + <> + + {children} + + ); + }, + }; +}); + +jest.mock('../create/form', () => { + return { + CreateCaseForm: () => { + return <>{'form'}; + }, + }; +}); + +jest.mock('../create/submit_button', () => { + return { + SubmitCaseButton: () => { + return <>{'Submit'}; + }, + }; +}); + +const onCloseFlyout = jest.fn(); +const onCaseCreated = jest.fn(); +const defaultProps = { + onCloseFlyout, + onCaseCreated, +}; + +describe('CreateCaseFlyout', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('renders', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj='create-case-flyout']`).exists()).toBeTruthy(); + }); + + it('Closing modal calls onCloseCaseModal', () => { + const wrapper = mount( + + + + ); + + wrapper.find('.euiFlyout__closeButton').first().simulate('click'); + expect(onCloseFlyout).toBeCalled(); + }); + + it('pass the correct props to FormContext component', () => { + const wrapper = mount( + + + + ); + + const props = wrapper.find('FormContext').props(); + expect(props).toEqual( + expect.objectContaining({ + onSuccess: onCaseCreated, + }) + ); + }); + + it('onSuccess called when creating a case', () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj='form-context-on-success']`).first().simulate('click'); + expect(onCaseCreated).toHaveBeenCalledWith({ id: 'case-id' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx new file mode 100644 index 00000000000000..cb3436f6ba3bcb --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx @@ -0,0 +1,69 @@ +/* + * 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 React, { memo } from 'react'; +import styled from 'styled-components'; +import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; + +import { FormContext } from '../create/form_context'; +import { CreateCaseForm } from '../create/form'; +import { SubmitCaseButton } from '../create/submit_button'; +import { Case } from '../../containers/types'; +import * as i18n from '../../translations'; + +export interface CreateCaseModalProps { + onCloseFlyout: () => void; + onCaseCreated: (theCase: Case) => void; +} + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + text-align: right; + `} +`; + +const StyledFlyout = styled(EuiFlyout)` + ${({ theme }) => ` + z-index: ${theme.eui.euiZModal}; + `} +`; + +// Adding bottom padding because timeline's +// bottom bar gonna hide the submit button. +const FormWrapper = styled.div` + padding-bottom: 50px; +`; + +const CreateCaseFlyoutComponent: React.FC = ({ + onCaseCreated, + onCloseFlyout, +}) => { + return ( + + + +

{i18n.CREATE_TITLE}

+
+
+ + + + + + + + + + +
+ ); +}; + +export const CreateCaseFlyout = memo(CreateCaseFlyoutComponent); + +CreateCaseFlyout.displayName = 'CreateCaseFlyout'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx index cc38e07cf49e4a..83b8870ab597d3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -19,6 +19,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { Case } from '../../containers/types'; +import { CaseType } from '../../../../../case/common/api'; const initialCaseValue: FormProps = { description: '', @@ -30,10 +31,15 @@ const initialCaseValue: FormProps = { }; interface Props { + caseType?: CaseType; onSuccess?: (theCase: Case) => void; } -export const FormContext: React.FC = ({ children, onSuccess }) => { +export const FormContext: React.FC = ({ + caseType = CaseType.individual, + children, + onSuccess, +}) => { const { connectors } = useConnectors(); const { connector: configurationConnector } = useCaseConfigure(); const { postCase } = usePostCase(); @@ -61,6 +67,7 @@ export const FormContext: React.FC = ({ children, onSuccess }) => { const updatedCase = await postCase({ ...dataWithoutConnectorId, + type: caseType, connector: connectorToUpdate, settings: { syncAlerts }, }); @@ -77,7 +84,7 @@ export const FormContext: React.FC = ({ children, onSuccess }) => { } } }, - [connectors, postCase, onSuccess, pushCaseToExternalService] + [caseType, connectors, postCase, onSuccess, pushCaseToExternalService] ); const { form } = useForm({ diff --git a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts index 909b49940e1899..81a7fe9cd9387b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CasePostRequest } from '../../../../../case/common/api'; +import { CasePostRequest, CaseType } from '../../../../../case/common/api'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; import { choices } from '../connectors/mock'; @@ -14,6 +14,7 @@ export const sampleData: CasePostRequest = { description: 'what a great description', tags: sampleTags, title: 'what a cool title', + type: CaseType.individual, connector: { fields: null, id: 'none', diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx index aa99543b49d2a7..ab30fe2979b9eb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx @@ -42,7 +42,7 @@ describe('StatusActionButton', () => { expect( wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType') - ).toBe('folderCheck'); + ).toBe('folderClosed'); }); it('it renders the correct button icon: status closed', () => { @@ -50,7 +50,7 @@ describe('StatusActionButton', () => { expect( wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType') - ).toBe('folderCheck'); + ).toBe('folderOpen'); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx index 2ec0ccd245b1ef..4ee69766fe1287 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx @@ -40,7 +40,7 @@ const StatusActionButtonComponent: React.FC = ({ return ( { wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); - expect(postComment.mock.calls[0][0]).toBe('new-case'); - expect(postComment.mock.calls[0][1]).toEqual({ + expect(postComment.mock.calls[0][0].caseId).toBe('new-case'); + expect(postComment.mock.calls[0][0].data).toEqual({ alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, type: 'alert', }); }); @@ -196,10 +200,14 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="all-cases-modal-button"]`).first().simulate('click'); - expect(postComment.mock.calls[0][0]).toBe('selected-case'); - expect(postComment.mock.calls[0][1]).toEqual({ + expect(postComment.mock.calls[0][0].caseId).toBe('selected-case'); + expect(postComment.mock.calls[0][0].data).toEqual({ alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, type: 'alert', }); }); @@ -208,7 +216,7 @@ describe('AddToCaseAction', () => { usePostCommentMock.mockImplementation(() => { return { ...defaultPostComment, - postComment: jest.fn().mockImplementation((caseId, data, updateCase) => updateCase()), + postComment: jest.fn().mockImplementation(({ caseId, data, updateCase }) => updateCase()), }; }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 6067f3fd7b15aa..aa9cec2d6b5b14 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -25,10 +25,11 @@ import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; import { getCaseDetailsUrl } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; -import { useCreateCaseModal } from '../use_create_case_modal'; import { useAllCasesModal } from '../use_all_cases_modal'; import { createUpdateSuccessToaster } from './helpers'; import * as i18n from './translations'; +import { useControl } from '../../../common/hooks/use_control'; +import { CreateCaseFlyout } from '../create/flyout'; interface AddToCaseActionProps { ariaLabel?: string; @@ -43,6 +44,7 @@ const AddToCaseActionComponent: React.FC = ({ }) => { const eventId = ecsRowData._id; const eventIndex = ecsRowData._index; + const rule = ecsRowData.signal?.rule; const { navigateToApp } = useKibana().services.application; const [, dispatchToaster] = useStateToaster(); @@ -61,29 +63,36 @@ const AddToCaseActionComponent: React.FC = ({ [navigateToApp] ); + const { + isControlOpen: isCreateCaseFlyoutOpen, + openControl: openCaseFlyoutOpen, + closeControl: closeCaseFlyoutOpen, + } = useControl(); + const attachAlertToCase = useCallback( (theCase: Case) => { - postComment( - theCase.id, - { + closeCaseFlyoutOpen(); + postComment({ + caseId: theCase.id, + data: { type: CommentType.alert, alertId: eventId, index: eventIndex ?? '', + rule: { + id: rule?.id != null ? rule.id[0] : null, + name: rule?.name != null ? rule.name[0] : null, + }, }, - () => + updateCase: () => dispatchToaster({ type: 'addToaster', toast: createUpdateSuccessToaster(theCase, onViewCaseClick), - }) - ); + }), + }); }, - [postComment, eventId, eventIndex, dispatchToaster, onViewCaseClick] + [closeCaseFlyoutOpen, postComment, eventId, eventIndex, rule, dispatchToaster, onViewCaseClick] ); - const { modal: createCaseModal, openModal: openCreateCaseModal } = useCreateCaseModal({ - onCaseCreated: attachAlertToCase, - }); - const onCaseClicked = useCallback( (theCase) => { /** @@ -92,13 +101,13 @@ const AddToCaseActionComponent: React.FC = ({ * We gonna open the create case modal. */ if (theCase == null) { - openCreateCaseModal(); + openCaseFlyoutOpen(); return; } attachAlertToCase(theCase); }, - [attachAlertToCase, openCreateCaseModal] + [attachAlertToCase, openCaseFlyoutOpen] ); const { modal: allCasesModal, openModal: openAllCaseModal } = useAllCasesModal({ @@ -107,8 +116,8 @@ const AddToCaseActionComponent: React.FC = ({ const addNewCaseClick = useCallback(() => { closePopover(); - openCreateCaseModal(); - }, [openCreateCaseModal, closePopover]); + openCaseFlyoutOpen(); + }, [openCaseFlyoutOpen, closePopover]); const addExistingCaseClick = useCallback(() => { closePopover(); @@ -173,7 +182,9 @@ const AddToCaseActionComponent: React.FC = ({ - {createCaseModal} + {isCreateCaseFlyoutOpen && ( + + )} {allCasesModal} ); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx index 8dd5080666cb38..2806e358fceee9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -14,11 +14,13 @@ import { CreateCaseForm } from '../create/form'; import { SubmitCaseButton } from '../create/submit_button'; import { Case } from '../../containers/types'; import * as i18n from '../../translations'; +import { CaseType } from '../../../../../case/common/api'; export interface CreateCaseModalProps { isModalOpen: boolean; onCloseCaseModal: () => void; onSuccess: (theCase: Case) => void; + caseType?: CaseType; } const Container = styled.div` @@ -32,6 +34,7 @@ const CreateModalComponent: React.FC = ({ isModalOpen, onCloseCaseModal, onSuccess, + caseType = CaseType.individual, }) => { return isModalOpen ? ( @@ -39,7 +42,7 @@ const CreateModalComponent: React.FC = ({ {i18n.CREATE_TITLE} - + diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx index 86313ebcb3bfad..3dc852a19e73fc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx @@ -6,11 +6,13 @@ */ import React, { useState, useCallback, useMemo } from 'react'; +import { CaseType } from '../../../../../case/common/api'; import { Case } from '../../containers/types'; import { CreateCaseModal } from './create_case_modal'; export interface UseCreateCaseModalProps { onCaseCreated: (theCase: Case) => void; + caseType?: CaseType; } export interface UseCreateCaseModalReturnedValues { modal: JSX.Element; @@ -19,7 +21,10 @@ export interface UseCreateCaseModalReturnedValues { openModal: () => void; } -export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) => { +export const useCreateCaseModal = ({ + caseType = CaseType.individual, + onCaseCreated, +}: UseCreateCaseModalProps) => { const [isModalOpen, setIsModalOpen] = useState(false); const closeModal = useCallback(() => setIsModalOpen(false), []); const openModal = useCallback(() => setIsModalOpen(true), []); @@ -35,6 +40,7 @@ export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) = () => ({ modal: ( void; + alertId: string; + index: string; + loadingAlertData: boolean; + ruleId: string; + ruleName: string; }): EuiCommentProps => { return { username: ( @@ -212,7 +231,15 @@ export const getAlertComment = ({ ), className: 'comment-alert', type: 'update', - event: , + event: ( + + ), 'data-test-subj': `${action.actionField[0]}-${action.action}-action-${action.actionId}`, timestamp: , timelineIcon: 'bell', @@ -222,23 +249,188 @@ export const getAlertComment = ({
- {alert != null ? ( - - ) : ( - - )} +
), }; }; + +export const toStringArray = (value: unknown): string[] => { + if (Array.isArray(value)) { + return value.reduce((acc, v) => { + if (v != null) { + switch (typeof v) { + case 'number': + case 'boolean': + return [...acc, v.toString()]; + case 'object': + try { + return [...acc, JSON.stringify(v)]; + } catch { + return [...acc, 'Invalid Object']; + } + case 'string': + return [...acc, v]; + default: + return [...acc, `${v}`]; + } + } + return acc; + }, []); + } else if (value == null) { + return []; + } else if (!Array.isArray(value) && typeof value === 'object') { + try { + return [JSON.stringify(value)]; + } catch { + return ['Invalid Object']; + } + } else { + return [`${value}`]; + } +}; + +export const formatAlertToEcsSignal = (alert: {}): Ecs => + Object.keys(alert).reduce((accumulator, key) => { + const item = get(alert, key); + if (item != null && isObject(item)) { + return { ...accumulator, [key]: formatAlertToEcsSignal(item) }; + } else if (Array.isArray(item) || isString(item) || isNumber(item)) { + return { ...accumulator, [key]: toStringArray(item) }; + } + return accumulator; + }, {} as Ecs); + +const EMPTY_ARRAY: TimelineNonEcsData[] = []; +export const getGeneratedAlertsAttachment = ({ + action, + alertIds, + ruleId, + ruleName, +}: { + action: CaseUserActions; + alertIds: string[]; + ruleId: string; + ruleName: string; +}): EuiCommentProps => { + const fetchEcsAlertsData = async (fetchAlertIds?: string[]): Promise => { + if (isEmpty(fetchAlertIds)) { + return []; + } + const alertResponse = await KibanaServices.get().http.fetch< + SearchResponse<{ '@timestamp': string; [key: string]: unknown }> + >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + method: 'POST', + body: JSON.stringify(buildAlertsQuery(fetchAlertIds ?? [])), + }); + return ( + alertResponse?.hits.hits.reduce( + (acc, { _id, _index, _source }) => [ + ...acc, + { + ...formatAlertToEcsSignal(_source as {}), + _id, + _index, + timestamp: _source['@timestamp'], + }, + ], + [] + ) ?? [] + ); + }; + return { + username: , + className: 'comment-alert', + type: 'update', + event: ( + + ), + 'data-test-subj': `${action.actionField[0]}-${action.action}-action-${action.actionId}`, + timestamp: , + timelineIcon: 'bell', + actions: ( + + + + + + + + + ), + }; +}; + +interface Signal { + rule: { + id: string; + name: string; + to: string; + from: string; + }; +} + +interface SignalHit { + _id: string; + _index: string; + _source: { + '@timestamp': string; + signal: Signal; + }; +} + +export interface Alert { + _id: string; + _index: string; + '@timestamp': string; + signal: Signal; + [key: string]: unknown; +} + +export const useFetchAlertData = (alertIds: string[]): [boolean, Record] => { + const { selectedPatterns } = useSourcererScope(SourcererScopeName.detections); + const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]); + + const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts( + alertsQuery, + selectedPatterns[0] + ); + + const alerts = useMemo( + () => + alertsData?.hits.hits.reduce>( + (acc, { _id, _index, _source }) => ({ + ...acc, + [_id]: { + ...formatAlertToEcsSignal(_source), + _id, + _index, + timestamp: _source['@timestamp'], + }, + }), + {} + ) ?? {}, + [alertsData?.hits.hits] + ); + + return [isLoadingAlerts, alerts]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx index 3b81fc0afccf3b..2a9f99465251b9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import classNames from 'classnames'; - import { EuiFlexGroup, EuiFlexItem, @@ -14,9 +12,12 @@ import { EuiCommentList, EuiCommentProps, } from '@elastic/eui'; +import classNames from 'classnames'; +import { isEmpty } from 'lodash'; import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; +import { isRight } from 'fp-ts/Either'; import * as i18n from './translations'; @@ -24,23 +25,31 @@ import { Case, CaseUserActions } from '../../containers/types'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser } from '../../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; -import { ActionConnector, CommentType } from '../../../../../case/common/api'; +import { + ActionConnector, + AlertCommentRequestRt, + CommentType, + ContextTypeUserRt, +} from '../../../../../case/common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; -import { Alert, OnUpdateFields } from '../case_view'; +import { OnUpdateFields } from '../case_view'; import { getConnectorLabelTitle, getLabelTitle, getPushedServiceLabelTitle, getPushInfo, getUpdateAction, - getAlertComment, + getAlertAttachment, + getGeneratedAlertsAttachment, + useFetchAlertData, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; import { UserActionMarkdown } from './user_action_markdown'; import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; +import { getManualAlertIdsWithNoRuleId } from '../case_view/helpers'; export interface UserActionTreeProps { caseServices: CaseServices; @@ -53,7 +62,6 @@ export interface UserActionTreeProps { onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; updateCase: (newCase: Case) => void; userCanCrud: boolean; - alerts: Record; onShowAlertDetails: (alertId: string, index: string) => void; } @@ -112,10 +120,9 @@ export const UserActionTree = React.memo( onUpdateField, updateCase, userCanCrud, - alerts, onShowAlertDetails, }: UserActionTreeProps) => { - const { commentId } = useParams<{ commentId?: string }>(); + const { commentId, subCaseId } = useParams<{ commentId?: string; subCaseId?: string }>(); const handlerTimeoutId = useRef(0); const addCommentRef = useRef(null); const [initLoading, setInitLoading] = useState(true); @@ -124,6 +131,10 @@ export const UserActionTree = React.memo( const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); + const [loadingAlertData, manualAlertsData] = useFetchAlertData( + getManualAlertIdsWithNoRuleId(caseData.comments) + ); + const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { @@ -218,9 +229,10 @@ export const UserActionTree = React.memo( onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_ID)} showLoading={false} + subCaseId={subCaseId} /> ), - [caseData.id, handleUpdate, userCanCrud, handleManageMarkdownEditId] + [caseData.id, handleUpdate, userCanCrud, handleManageMarkdownEditId, subCaseId] ); useEffect(() => { @@ -279,11 +291,16 @@ export const UserActionTree = React.memo( const userActions: EuiCommentProps[] = useMemo( () => caseUserActions.reduce( + // eslint-disable-next-line complexity (comments, action, index) => { // Comment creation if (action.commentId != null && action.action === 'create') { const comment = caseData.comments.find((c) => c.id === action.commentId); - if (comment != null && comment.type === CommentType.user) { + if ( + comment != null && + isRight(ContextTypeUserRt.decode(comment)) && + comment.type === CommentType.user + ) { return [ ...comments, { @@ -335,16 +352,65 @@ export const UserActionTree = React.memo( ), }, ]; - // TODO: need to handle CommentType.generatedAlert here to - } else if (comment != null && comment.type === CommentType.alert) { + } else if ( + comment != null && + isRight(AlertCommentRequestRt.decode(comment)) && + comment.type === CommentType.alert + ) { // TODO: clean this up const alertId = Array.isArray(comment.alertId) ? comment.alertId.length > 0 ? comment.alertId[0] : '' : comment.alertId; - const alert = alerts[alertId]; - return [...comments, getAlertComment({ action, alert, onShowAlertDetails })]; + + const alertIndex = Array.isArray(comment.index) + ? comment.index.length > 0 + ? comment.index[0] + : '' + : comment.index; + + if (isEmpty(alertId)) { + return comments; + } + + const ruleId = comment?.rule?.id ?? manualAlertsData[alertId]?.rule?.id?.[0] ?? ''; + const ruleName = + comment?.rule?.name ?? + manualAlertsData[alertId]?.rule?.name?.[0] ?? + i18n.UNKNOWN_RULE; + + return [ + ...comments, + getAlertAttachment({ + action, + alertId, + index: alertIndex, + loadingAlertData, + ruleId, + ruleName, + onShowAlertDetails, + }), + ]; + } else if (comment != null && comment.type === CommentType.generatedAlert) { + // TODO: clean this up + const alertIds = Array.isArray(comment.alertId) + ? comment.alertId + : [comment.alertId]; + + if (isEmpty(alertIds)) { + return comments; + } + + return [ + ...comments, + getGeneratedAlertsAttachment({ + action, + alertIds, + ruleId: comment.rule?.id ?? '', + ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, + }), + ]; } } @@ -438,10 +504,11 @@ export const UserActionTree = React.memo( handleManageQuote, handleSaveComment, isLoadingIds, + loadingAlertData, + manualAlertsData, manageMarkdownEditIds, selectedOutlineCommentId, userCanCrud, - alerts, onShowAlertDetails, ] ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts index ede216d562f117..46f36615b1a4ea 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts @@ -42,6 +42,19 @@ export const ALERT_COMMENT_LABEL_TITLE = i18n.translate( } ); +export const GENERATED_ALERT_COMMENT_LABEL_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.generatedAlertCommentLabelTitle', + { + defaultMessage: 'were added from', + } +); + +export const GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE = (totalCount: number) => + i18n.translate('xpack.securitySolution.case.caseView.generatedAlertCountCommentLabelTitle', { + values: { totalCount }, + defaultMessage: `{totalCount} {totalCount, plural, =1 {alert} other {alerts}}`, + }); + export const ALERT_RULE_DELETED_COMMENT_LABEL = i18n.translate( 'xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle', { @@ -56,9 +69,16 @@ export const SHOW_ALERT_TOOLTIP = i18n.translate( } ); -export const ALERT_NOT_FOUND_TOOLTIP = i18n.translate( - 'xpack.securitySolution.case.caseView.showAlertDeletedTooltip', +export const SEND_ALERT_TO_TIMELINE = i18n.translate( + 'xpack.securitySolution.case.caseView.sendAlertToTimelineTooltip', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const UNKNOWN_RULE = i18n.translate( + 'xpack.securitySolution.case.caseView.unknownRule.label', { - defaultMessage: 'Alert not found', + defaultMessage: 'Unknown rule', } ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx index f0ff145a269ff4..228945bacf8a49 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx @@ -11,19 +11,14 @@ import { mount } from 'enzyme'; import { TestProviders } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; import { AlertCommentEvent } from './user_action_alert_comment_event'; +import { CommentType } from '../../../../../case/common/api'; const props = { - alert: { - _id: 'alert-id-1', - _index: 'alert-index-1', - '@timestamp': '2021-01-07T13:58:31.487Z', - rule: { - id: 'rule-id-1', - name: 'Awesome rule', - from: '2021-01-07T13:58:31.487Z', - to: '2021-01-07T14:58:31.487Z', - }, - }, + alertId: 'alert-id-1', + ruleId: 'rule-id-1', + ruleName: 'Awesome rule', + alertsCount: 1, + commentType: CommentType.alert, }; jest.mock('../../../common/lib/kibana'); @@ -54,7 +49,8 @@ describe('UserActionAvatar ', () => { it('does NOT render the link when the alert is undefined', async () => { const wrapper = mount( - + {/* @ts-expect-error */} + ); @@ -62,13 +58,13 @@ describe('UserActionAvatar ', () => { wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists() ).toBeFalsy(); - expect(wrapper.text()).toBe('added an alert'); + expect(wrapper.text()).toBe('added an alert from '); }); it('does NOT render the link when the rule is undefined', async () => { const alert = { - _id: 'alert-id-1', - _index: 'alert-index-1', + alertId: 'alert-id-1', + commentType: CommentType.alert, }; const wrapper = mount( @@ -82,7 +78,7 @@ describe('UserActionAvatar ', () => { wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists() ).toBeFalsy(); - expect(wrapper.text()).toBe('added an alert'); + expect(wrapper.text()).toBe('added an alert from '); }); it('navigate to app on link click', async () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx index 183116167f84a2..2a604b7c54d6b4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx @@ -6,24 +6,36 @@ */ import React, { memo, useCallback } from 'react'; -import { EuiLink } from '@elastic/eui'; +import { EuiText, EuiLoadingSpinner } from '@elastic/eui'; import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; -import { getRuleDetailsUrl } from '../../../common/components/link_to'; +import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; -import { Alert } from '../case_view'; import * as i18n from './translations'; +import { CommentType } from '../../../../../case/common/api'; +import { LinkAnchor } from '../../../common/components/links'; interface Props { - alert: Alert | undefined; + alertId: string; + commentType: CommentType; + ruleId: string; + ruleName: string; + alertsCount?: number; + loadingAlertData?: boolean; } -const AlertCommentEventComponent: React.FC = ({ alert }) => { - const ruleName = alert?.rule?.name ?? null; - const ruleId = alert?.rule?.id ?? null; +const AlertCommentEventComponent: React.FC = ({ + alertId, + loadingAlertData = false, + ruleId, + ruleName, + alertsCount, + commentType, +}) => { const { navigateToApp } = useKibana().services.application; + const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.detections); const onLinkClick = useCallback( (ev: { preventDefault: () => void }) => { @@ -35,15 +47,37 @@ const AlertCommentEventComponent: React.FC = ({ alert }) => { [ruleId, navigateToApp] ); - return ruleId != null && ruleName != null ? ( + return commentType !== CommentType.generatedAlert ? ( <> {`${i18n.ALERT_COMMENT_LABEL_TITLE} `} - - {ruleName} - + {loadingAlertData && } + {!loadingAlertData && ruleId !== '' && ( + + {ruleName} + + )} + {!loadingAlertData && ruleId === '' && {ruleName}} ) : ( - <>{i18n.ALERT_RULE_DELETED_COMMENT_LABEL} + <> + {i18n.GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE(alertsCount ?? 0)}{' '} + {i18n.GENERATED_ALERT_COMMENT_LABEL_TITLE}{' '} + {loadingAlertData && } + {!loadingAlertData && ruleId !== '' && ( + + {ruleName} + + )} + {!loadingAlertData && ruleId === '' && {ruleName}} + ); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx index 4bd4734e30671e..ff4e151197464c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx @@ -18,24 +18,26 @@ interface UserActionCopyLinkProps { id: string; } -const UserActionCopyLinkComponent = ({ id }: UserActionCopyLinkProps) => { - const { detailName: caseId } = useParams<{ detailName: string }>(); +const UserActionCopyLinkComponent = ({ id: commentId }: UserActionCopyLinkProps) => { + const { detailName: caseId, subCaseId } = useParams<{ detailName: string; subCaseId?: string }>(); const { formatUrl } = useFormatUrl(SecurityPageName.case); const handleAnchorLink = useCallback(() => { copy( - formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId: id }), { absolute: true }) + formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId, subCaseId }), { + absolute: true, + }) ); - }, [caseId, formatUrl, id]); + }, [caseId, commentId, formatUrl, subCaseId]); return ( {i18n.COPY_REFERENCE_LINK}

}>
); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx index 5d619a39d0e79a..789a6eb68e0fc7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx @@ -8,20 +8,24 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { UserActionShowAlert } from './user_action_show_alert'; +import { RuleEcs } from '../../../../common/ecs/rule'; const props = { id: 'action-id', + alertId: 'alert-id', + index: 'alert-index', alert: { _id: 'alert-id', _index: 'alert-index', - '@timestamp': '2021-01-07T13:58:31.487Z', + timestamp: '2021-01-07T13:58:31.487Z', rule: { - id: 'rule-id', - name: 'Awesome Rule', - from: '2021-01-07T13:58:31.487Z', - to: '2021-01-07T14:58:31.487Z', - }, + id: ['rule-id'], + name: ['Awesome Rule'], + from: ['2021-01-07T13:58:31.487Z'], + to: ['2021-01-07T14:58:31.487Z'], + } as RuleEcs, }, + onShowAlertDetails: jest.fn(), }; describe('UserActionShowAlert ', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx index ea4994d1c80983..4f5ce00806417b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx @@ -7,25 +7,24 @@ import React, { memo, useCallback } from 'react'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; -import deepEqual from 'fast-deep-equal'; - -import { Alert } from '../case_view'; import * as i18n from './translations'; interface UserActionShowAlertProps { id: string; - alert: Alert; + alertId: string; + index: string; onShowAlertDetails: (alertId: string, index: string) => void; } const UserActionShowAlertComponent = ({ id, - alert, + alertId, + index, onShowAlertDetails, }: UserActionShowAlertProps) => { - const onClick = useCallback(() => onShowAlertDetails(alert._id, alert._index), [ - alert._id, - alert._index, + const onClick = useCallback(() => onShowAlertDetails(alertId, index), [ + alertId, + index, onShowAlertDetails, ]); return ( @@ -41,10 +40,4 @@ const UserActionShowAlertComponent = ({ ); }; -export const UserActionShowAlert = memo( - UserActionShowAlertComponent, - (prevProps, nextProps) => - prevProps.id === nextProps.id && - deepEqual(prevProps.alert, nextProps.alert) && - prevProps.onShowAlertDetails === nextProps.onShowAlertDetails -); +export const UserActionShowAlert = memo(UserActionShowAlertComponent); diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index 00a45aadd2ae08..c87e210b42bc05 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { assign } from 'lodash'; + import { CasePatchRequest, CasePostRequest, @@ -16,6 +18,9 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, + SubCasePatchRequest, + SubCaseResponse, + SubCasesResponse, User, } from '../../../../case/common/api'; @@ -25,6 +30,8 @@ import { CASE_STATUS_URL, CASE_TAGS_URL, CASES_URL, + SUB_CASE_DETAILS_URL, + SUB_CASES_PATCH_DEL_URL, } from '../../../../case/common/constants'; import { @@ -32,6 +39,8 @@ import { getCasePushUrl, getCaseDetailsUrl, getCaseUserActionUrl, + getSubCaseDetailsUrl, + getSubCaseUserActionUrl, } from '../../../../case/common/api/helpers'; import { KibanaServices } from '../../common/lib/kibana'; @@ -73,6 +82,34 @@ export const getCase = async ( return convertToCamelCase(decodeCaseResponse(response)); }; +export const getSubCase = async ( + caseId: string, + subCaseId: string, + includeComments: boolean = true, + signal: AbortSignal +): Promise => { + const [caseResponse, subCaseResponse] = await Promise.all([ + KibanaServices.get().http.fetch(getCaseDetailsUrl(caseId), { + method: 'GET', + query: { + includeComments: false, + }, + signal, + }), + KibanaServices.get().http.fetch(getSubCaseDetailsUrl(caseId, subCaseId), { + method: 'GET', + query: { + includeComments, + }, + signal, + }), + ]); + const response = assign(caseResponse, subCaseResponse); + const subCaseIndex = response.subCaseIds?.findIndex((scId) => scId === response.id) ?? -1; + response.title = `${response.title}${subCaseIndex >= 0 ? ` ${subCaseIndex + 1}` : ''}`; + return convertToCamelCase(decodeCaseResponse(response)); +}; + export const getCasesStatus = async (signal: AbortSignal): Promise => { const response = await KibanaServices.get().http.fetch(CASE_STATUS_URL, { method: 'GET', @@ -111,6 +148,21 @@ export const getCaseUserActions = async ( return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[]; }; +export const getSubCaseUserActions = async ( + caseId: string, + subCaseId: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + getSubCaseUserActionUrl(caseId, subCaseId), + { + method: 'GET', + signal, + } + ); + return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[]; +}; + export const getCases = async ({ filterOptions = { search: '', @@ -167,6 +219,35 @@ export const patchCase = async ( return convertToCamelCase(decodeCasesResponse(response)); }; +export const patchSubCase = async ( + caseId: string, + subCaseId: string, + updatedSubCase: Pick, + version: string, + signal: AbortSignal +): Promise => { + const subCaseResponse = await KibanaServices.get().http.fetch( + SUB_CASE_DETAILS_URL, + { + method: 'PATCH', + body: JSON.stringify({ cases: [{ ...updatedSubCase, id: caseId, version }] }), + signal, + } + ); + const caseResponse = await KibanaServices.get().http.fetch( + getCaseDetailsUrl(caseId), + { + method: 'GET', + query: { + includeComments: false, + }, + signal, + } + ); + const response = subCaseResponse.map((subCaseResp) => assign(caseResponse, subCaseResp)); + return convertToCamelCase(decodeCasesResponse(response)); +}; + export const patchCasesStatus = async ( cases: BulkUpdateStatus[], signal: AbortSignal @@ -182,13 +263,15 @@ export const patchCasesStatus = async ( export const postComment = async ( newComment: CommentRequest, caseId: string, - signal: AbortSignal + signal: AbortSignal, + subCaseId?: string ): Promise => { const response = await KibanaServices.get().http.fetch( `${CASES_URL}/${caseId}/comments`, { method: 'POST', body: JSON.stringify(newComment), + ...(subCaseId ? { query: { subCaseId } } : {}), signal, } ); @@ -200,7 +283,8 @@ export const patchComment = async ( commentId: string, commentUpdate: string, version: string, - signal: AbortSignal + signal: AbortSignal, + subCaseId?: string ): Promise => { const response = await KibanaServices.get().http.fetch(getCaseCommentsUrl(caseId), { method: 'PATCH', @@ -210,6 +294,7 @@ export const patchComment = async ( id: commentId, version, }), + ...(subCaseId ? { query: { subCaseId } } : {}), signal, }); return convertToCamelCase(decodeCaseResponse(response)); @@ -224,6 +309,15 @@ export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promi return response; }; +export const deleteSubCases = async (caseIds: string[], signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(SUB_CASES_PATCH_DEL_URL, { + method: 'DELETE', + query: { ids: JSON.stringify(caseIds) }, + signal, + }); + return response; +}; + export const pushCase = async ( caseId: string, connectorId: string, diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index 80d4816bedd53d..d8692da986cbef 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -26,6 +26,7 @@ import { ConnectorTypes } from '../../../../case/common/api/connectors'; export { connectorsMock } from './configure/mock'; export const basicCaseId = 'basic-case-id'; +export const basicSubCaseId = 'basic-sub-case-id'; const basicCommentId = 'basic-comment-id'; const basicCreatedAt = '2020-02-19T23:06:33.798Z'; const basicUpdatedAt = '2020-02-20T15:02:57.995Z'; @@ -63,6 +64,10 @@ export const alertComment: Comment = { createdBy: elasticUser, pushedAt: null, pushedBy: null, + rule: { + id: 'rule-id-1', + name: 'Awesome rule', + }, updatedAt: null, updatedBy: null, version: 'WzQ3LDFc', @@ -95,6 +100,7 @@ export const basicCase: Case = { settings: { syncAlerts: true, }, + subCaseIds: [], }; export const basicCasePost: Case = { @@ -217,7 +223,7 @@ export const basicCaseSnake: CaseResponse = { external_service: null, updated_at: basicUpdatedAt, updated_by: elasticUserSnake, -}; +} as CaseResponse; export const casesStatusSnake: CasesStatusResponse = { count_closed_cases: 130, diff --git a/x-pack/plugins/security_solution/public/cases/containers/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/translations.ts index 75939b46b1f771..c79b897ba43a14 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/translations.ts @@ -53,7 +53,20 @@ export const REOPENED_CASES = ({ }) => i18n.translate('xpack.securitySolution.containers.case.reopenedCases', { values: { caseTitle, totalCases }, - defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const MARK_IN_PROGRESS_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.securitySolution.containers.case.markInProgressCases', { + values: { caseTitle, totalCases }, + defaultMessage: + 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', }); export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index 30ea8344434686..d2931a790bd798 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -18,7 +18,9 @@ import { AssociationType, } from '../../../../case/common/api'; -export { CaseConnector, ActionConnector } from '../../../../case/common/api'; +export { CaseConnector, ActionConnector, CaseStatuses } from '../../../../case/common/api'; + +export type AllCaseType = AssociationType & CaseType; export type Comment = CommentRequest & { associationType: AssociationType; @@ -52,26 +54,37 @@ export interface CaseExternalService { externalTitle: string; externalUrl: string; } -export interface Case { + +interface BasicCase { id: string; closedAt: string | null; closedBy: ElasticUser | null; comments: Comment[]; - connector: CaseConnector; createdAt: string; createdBy: ElasticUser; - description: string; - externalService: CaseExternalService | null; status: CaseStatuses; - tags: string[]; title: string; totalAlerts: number; totalComment: number; - type: CaseType; updatedAt: string | null; updatedBy: ElasticUser | null; version: string; +} + +export interface SubCase extends BasicCase { + associationType: AssociationType; + caseParentId: string; +} + +export interface Case extends BasicCase { + connector: CaseConnector; + description: string; + externalService: CaseExternalService | null; + subCases?: SubCase[] | null; + subCaseIds: string[]; settings: CaseAttributes['settings']; + tags: string[]; + type: CaseType; } export interface QueryParams { @@ -138,6 +151,7 @@ export interface ActionLicense { export interface DeleteCase { id: string; title?: string; + type?: CaseType; } export interface FieldMappings { @@ -153,7 +167,7 @@ export type UpdateKey = keyof Pick< export interface UpdateByKey { updateKey: UpdateKey; updateValue: CasePatchRequest[UpdateKey]; - fetchCaseUserActions?: (caseId: string) => void; + fetchCaseUserActions?: (caseId: string, subCaseId?: string) => void; updateCase?: (newCase: Case) => void; caseData: Case; onSuccess?: () => void; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx index 5fd181a4bbd414..0fe45aaab799b9 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx @@ -62,6 +62,24 @@ export interface UseUpdateCases extends UpdateState { dispatchResetIsUpdated: () => void; } +const getStatusToasterMessage = ( + status: CaseStatuses, + messageArgs: { + totalCases: number; + caseTitle?: string; + } +): string => { + if (status === CaseStatuses.open) { + return i18n.REOPENED_CASES(messageArgs); + } else if (status === CaseStatuses['in-progress']) { + return i18n.MARK_IN_PROGRESS_CASES(messageArgs); + } else if (status === CaseStatuses.closed) { + return i18n.CLOSED_CASES(messageArgs); + } + + return ''; +}; + export const useUpdateCases = (): UseUpdateCases => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, @@ -70,7 +88,7 @@ export const useUpdateCases = (): UseUpdateCases => { }); const [, dispatchToaster] = useStateToaster(); - const dispatchUpdateCases = useCallback((cases: BulkUpdateStatus[]) => { + const dispatchUpdateCases = useCallback((cases: BulkUpdateStatus[], action: string) => { let cancel = false; const abortCtrl = new AbortController(); @@ -83,14 +101,16 @@ export const useUpdateCases = (): UseUpdateCases => { const firstTitle = patchResponse[0].title; dispatch({ type: 'FETCH_SUCCESS', payload: true }); + const messageArgs = { totalCases: resultCount, caseTitle: resultCount === 1 ? firstTitle : '', }; + const message = - resultCount && patchResponse[0].status === CaseStatuses.open - ? i18n.REOPENED_CASES(messageArgs) - : i18n.CLOSED_CASES(messageArgs); + action === 'status' + ? getStatusToasterMessage(patchResponse[0].status, messageArgs) + : ''; displaySuccessToast(message, dispatchToaster); } @@ -123,7 +143,7 @@ export const useUpdateCases = (): UseUpdateCases => { id: theCase.id, version: theCase.version, })); - dispatchUpdateCases(updateCasesStatus); + dispatchUpdateCases(updateCasesStatus, 'status'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { ...state, updateBulkStatus, dispatchResetIsUpdated }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx index b777b16b1c0c1c..923c20dcf8ebd5 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx @@ -12,7 +12,7 @@ import { useStateToaster, } from '../../common/components/toasters'; import * as i18n from './translations'; -import { deleteCases } from './api'; +import { deleteCases, deleteSubCases } from './api'; import { DeleteCase } from './types'; interface DeleteState { @@ -87,7 +87,13 @@ export const useDeleteCases = (): UseDeleteCase => { try { dispatch({ type: 'FETCH_INIT' }); const caseIds = cases.map((theCase) => theCase.id); - await deleteCases(caseIds, abortCtrl.signal); + // We don't allow user batch delete sub cases on UI at the moment. + if (cases[0].type != null || cases.length > 1) { + await deleteCases(caseIds, abortCtrl.signal); + } else { + await deleteSubCases(caseIds, abortCtrl.signal); + } + if (!cancel) { dispatch({ type: 'FETCH_SUCCESS', payload: true }); displaySuccessToast( diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx index 45827a4bebff80..1c4476e3cb2b7c 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import { useEffect, useReducer, useCallback } from 'react'; +import { isEmpty } from 'lodash'; +import { useEffect, useReducer, useCallback, useRef } from 'react'; import { CaseStatuses, CaseType } from '../../../../case/common/api'; import { Case } from './types'; import * as i18n from './translations'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { getCase } from './api'; +import { getCase, getSubCase } from './api'; import { getNoneConnector } from '../components/configure_cases/utils'; interface CaseState { @@ -77,6 +78,7 @@ export const initialData: Case = { updatedAt: null, updatedBy: null, version: '', + subCaseIds: [], settings: { syncAlerts: true, }, @@ -87,31 +89,32 @@ export interface UseGetCase extends CaseState { updateCase: (newCase: Case) => void; } -export const useGetCase = (caseId: string): UseGetCase => { +export const useGetCase = (caseId: string, subCaseId?: string): UseGetCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: true, isError: false, data: initialData, }); const [, dispatchToaster] = useStateToaster(); + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const updateCase = useCallback((newCase: Case) => { dispatch({ type: 'UPDATE_CASE', payload: newCase }); }, []); const callFetch = useCallback(async () => { - let didCancel = false; - const abortCtrl = new AbortController(); - const fetchData = async () => { dispatch({ type: 'FETCH_INIT' }); try { - const response = await getCase(caseId, true, abortCtrl.signal); - if (!didCancel) { + const response = await (subCaseId + ? getSubCase(caseId, subCaseId, true, abortCtrl.current.signal) + : getCase(caseId, true, abortCtrl.current.signal)); + if (!didCancel.current) { dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { - if (!didCancel) { + if (!didCancel.current) { errorToToaster({ title: i18n.ERROR_TITLE, error: error.body && error.body.message ? new Error(error.body.message) : error, @@ -121,17 +124,22 @@ export const useGetCase = (caseId: string): UseGetCase => { } } }; + didCancel.current = false; + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId]); + }, [caseId, subCaseId]); useEffect(() => { - callFetch(); + if (!isEmpty(caseId)) { + callFetch(); + } + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId]); + }, [caseId, subCaseId]); return { ...state, fetchCase: callFetch, updateCase }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx index 8ebd46e64296f6..12e5f6643351ff 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx @@ -6,12 +6,12 @@ */ import { isEmpty, uniqBy } from 'lodash/fp'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { CaseFullExternalService } from '../../../../case/common/api/cases'; -import { getCaseUserActions } from './api'; +import { getCaseUserActions, getSubCaseUserActions } from './api'; import * as i18n from './translations'; import { CaseConnector, CaseExternalService, CaseUserActions, ElasticUser } from './types'; import { convertToCamelCase, parseString } from './utils'; @@ -46,7 +46,7 @@ export const initialData: CaseUserActionsState = { }; export interface UseGetCaseUserActions extends CaseUserActionsState { - fetchCaseUserActions: (caseId: string) => void; + fetchCaseUserActions: (caseId: string, subCaseId?: string) => void; } const getExternalService = (value: string): CaseExternalService | null => @@ -238,26 +238,29 @@ export const getPushedInfo = ( export const useGetCaseUserActions = ( caseId: string, - caseConnectorId: string + caseConnectorId: string, + subCaseId?: string ): UseGetCaseUserActions => { const [caseUserActionsState, setCaseUserActionsState] = useState( initialData ); - + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const [, dispatchToaster] = useStateToaster(); const fetchCaseUserActions = useCallback( - (thisCaseId: string) => { - let didCancel = false; - const abortCtrl = new AbortController(); + (thisCaseId: string, thisSubCaseId?: string) => { const fetchData = async () => { - setCaseUserActionsState({ - ...caseUserActionsState, - isLoading: true, - }); try { - const response = await getCaseUserActions(thisCaseId, abortCtrl.signal); - if (!didCancel) { + setCaseUserActionsState({ + ...caseUserActionsState, + isLoading: true, + }); + + const response = await (thisSubCaseId + ? getSubCaseUserActions(thisCaseId, thisSubCaseId, abortCtrl.current.signal) + : getCaseUserActions(thisCaseId, abortCtrl.current.signal)); + if (!didCancel.current) { // Attention Future developer // We are removing the first item because it will always be the creation of the case // and we do not want it to simplify our life @@ -265,7 +268,11 @@ export const useGetCaseUserActions = ( ? uniqBy('actionBy.username', response).map((cau) => cau.actionBy) : []; - const caseUserActions = !isEmpty(response) ? response.slice(1) : []; + const caseUserActions = !isEmpty(response) + ? thisSubCaseId + ? response + : response.slice(1) + : []; setCaseUserActionsState({ caseUserActions, ...getPushedInfo(caseUserActions, caseConnectorId), @@ -275,7 +282,7 @@ export const useGetCaseUserActions = ( }); } } catch (error) { - if (!didCancel) { + if (!didCancel.current) { errorToToaster({ title: i18n.ERROR_TITLE, error: error.body && error.body.message ? new Error(error.body.message) : error, @@ -292,21 +299,24 @@ export const useGetCaseUserActions = ( } } }; + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; }, // eslint-disable-next-line react-hooks/exhaustive-deps - [caseUserActionsState, caseConnectorId] + [caseConnectorId] ); useEffect(() => { if (!isEmpty(caseId)) { - fetchCaseUserActions(caseId); + fetchCaseUserActions(caseId, subCaseId); } + + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId, caseConnectorId]); + }, [caseId, subCaseId]); return { ...caseUserActionsState, fetchCaseUserActions }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx index f9d4454f63ffb6..42cd0deafa048f 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx @@ -9,7 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { CommentType } from '../../../../case/common/api'; import { usePostComment, UsePostComment } from './use_post_comment'; -import { basicCaseId } from './mock'; +import { basicCaseId, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); @@ -40,7 +40,7 @@ describe('usePostComment', () => { }); }); - it('calls postComment with correct arguments', async () => { + it('calls postComment with correct arguments - case', async () => { const spyOnPostCase = jest.spyOn(api, 'postComment'); await act(async () => { @@ -49,9 +49,38 @@ describe('usePostComment', () => { ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); + await waitForNextUpdate(); + expect(spyOnPostCase).toBeCalledWith(samplePost, basicCaseId, abortCtrl.signal, undefined); + }); + }); + + it('calls postComment with correct arguments - sub case', async () => { + const spyOnPostCase = jest.spyOn(api, 'postComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment() + ); + await waitForNextUpdate(); + + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + subCaseId: basicSubCaseId, + }); await waitForNextUpdate(); - expect(spyOnPostCase).toBeCalledWith(samplePost, basicCaseId, abortCtrl.signal); + expect(spyOnPostCase).toBeCalledWith( + samplePost, + basicCaseId, + abortCtrl.signal, + basicSubCaseId + ); }); }); @@ -61,7 +90,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); await waitForNextUpdate(); expect(result.current).toEqual({ isLoading: false, @@ -77,7 +110,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); expect(result.current.isLoading).toBe(true); }); @@ -94,7 +131,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); expect(result.current).toEqual({ isLoading: false, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx index f2bd9d3f41f3ce..8fc8053c14f70d 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx @@ -42,8 +42,14 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta } }; +interface PostComment { + caseId: string; + data: CommentRequest; + updateCase?: (newCase: Case) => void; + subCaseId?: string; +} export interface UsePostComment extends NewCommentState { - postComment: (caseId: string, data: CommentRequest, updateCase?: (newCase: Case) => void) => void; + postComment: (args: PostComment) => void; } export const usePostComment = (): UsePostComment => { @@ -54,13 +60,13 @@ export const usePostComment = (): UsePostComment => { const [, dispatchToaster] = useStateToaster(); const postMyComment = useCallback( - async (caseId: string, data: CommentRequest, updateCase?: (newCase: Case) => void) => { + async ({ caseId, data, updateCase, subCaseId }: PostComment) => { let cancel = false; const abortCtrl = new AbortController(); try { dispatch({ type: 'FETCH_INIT' }); - const response = await postComment(data, caseId, abortCtrl.signal); + const response = await postComment(data, caseId, abortCtrl.signal, subCaseId); if (!cancel) { dispatch({ type: 'FETCH_SUCCESS' }); if (updateCase) { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx index 62560244fe9c80..0adf2cc0bf92ab 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateCase, UseUpdateCase } from './use_update_case'; -import { basicCase } from './mock'; +import { basicCase, basicSubCaseId } from './mock'; import * as api from './api'; import { UpdateKey } from './types'; @@ -84,7 +84,27 @@ describe('useUpdateCase', () => { isError: false, updateCaseProperty: result.current.updateCaseProperty, }); - expect(fetchCaseUserActions).toBeCalledWith(basicCase.id); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, undefined); + expect(updateCase).toBeCalledWith(basicCase); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('patch sub case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id, subCaseId: basicSubCaseId }) + ); + await waitForNextUpdate(); + result.current.updateCaseProperty(sampleUpdate); + await waitForNextUpdate(); + expect(result.current).toEqual({ + updateKey: null, + isLoading: false, + isError: false, + updateCaseProperty: result.current.updateCaseProperty, + }); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, basicSubCaseId); expect(updateCase).toBeCalledWith(basicCase); expect(onSuccess).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx index b2b919ae1422b0..23a23caeb71bdf 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import { useReducer, useCallback } from 'react'; +import { useReducer, useCallback, useEffect, useRef } from 'react'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { patchCase } from './api'; -import { UpdateKey, UpdateByKey } from './types'; +import { patchCase, patchSubCase } from './api'; +import { UpdateKey, UpdateByKey, CaseStatuses } from './types'; import * as i18n from './translations'; import { createUpdateSuccessToaster } from './utils'; @@ -57,13 +57,21 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => export interface UseUpdateCase extends NewCaseState { updateCaseProperty: (updates: UpdateByKey) => void; } -export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => { +export const useUpdateCase = ({ + caseId, + subCaseId, +}: { + caseId: string; + subCaseId?: string; +}): UseUpdateCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, updateKey: null, }); const [, dispatchToaster] = useStateToaster(); + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const dispatchUpdateCaseProperty = useCallback( async ({ @@ -75,20 +83,27 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => onSuccess, onError, }: UpdateByKey) => { - let cancel = false; - const abortCtrl = new AbortController(); - try { + didCancel.current = false; + abortCtrl.current = new AbortController(); dispatch({ type: 'FETCH_INIT', payload: updateKey }); - const response = await patchCase( - caseId, - { [updateKey]: updateValue }, - caseData.version, - abortCtrl.signal - ); - if (!cancel) { + const response = await (updateKey === 'status' && subCaseId + ? patchSubCase( + caseId, + subCaseId, + { status: updateValue as CaseStatuses }, + caseData.version, + abortCtrl.current.signal + ) + : patchCase( + caseId, + { [updateKey]: updateValue }, + caseData.version, + abortCtrl.current.signal + )); + if (!didCancel.current) { if (fetchCaseUserActions != null) { - fetchCaseUserActions(caseId); + fetchCaseUserActions(caseId, subCaseId); } if (updateCase != null) { updateCase(response[0]); @@ -104,26 +119,31 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => } } } catch (error) { - if (!cancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + if (!didCancel.current) { + if (error.name !== 'AbortError') { + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } dispatch({ type: 'FETCH_FAILURE' }); if (onError) { onError(); } } } - return () => { - cancel = true; - abortCtrl.abort(); - }; }, // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [caseId, subCaseId] ); + useEffect(() => { + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; + }, []); + return { ...state, updateCaseProperty: dispatchUpdateCaseProperty }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx index d7d98879459fe4..9ff266ad9c9881 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateComment, UseUpdateComment } from './use_update_comment'; -import { basicCase, basicCaseCommentPatch } from './mock'; +import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); @@ -43,7 +43,7 @@ describe('useUpdateComment', () => { }); }); - it('calls patchComment with correct arguments', async () => { + it('calls patchComment with correct arguments - case', async () => { const spyOnPatchComment = jest.spyOn(api, 'patchComment'); await act(async () => { @@ -59,7 +59,30 @@ describe('useUpdateComment', () => { basicCase.comments[0].id, 'updated comment', basicCase.comments[0].version, - abortCtrl.signal + abortCtrl.signal, + undefined + ); + }); + }); + + it('calls patchComment with correct arguments - sub case', async () => { + const spyOnPatchComment = jest.spyOn(api, 'patchComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + + result.current.patchComment({ ...sampleUpdate, subCaseId: basicSubCaseId }); + await waitForNextUpdate(); + expect(spyOnPatchComment).toBeCalledWith( + basicCase.id, + basicCase.comments[0].id, + 'updated comment', + basicCase.comments[0].version, + abortCtrl.signal, + basicSubCaseId ); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx index 6222d993bb798b..e36b21823310eb 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx @@ -57,6 +57,7 @@ interface UpdateComment { commentId: string; commentUpdate: string; fetchUserActions: () => void; + subCaseId?: string; updateCase: (newCase: Case) => void; version: string; } @@ -78,6 +79,7 @@ export const useUpdateComment = (): UseUpdateComment => { commentId, commentUpdate, fetchUserActions, + subCaseId, updateCase, version, }: UpdateComment) => { @@ -90,7 +92,8 @@ export const useUpdateComment = (): UseUpdateComment => { commentId, commentUpdate, version, - abortCtrl.signal + abortCtrl.signal, + subCaseId ); if (!cancel) { updateCase(response); diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx index 701ecdf8580f00..edb84db89b878a 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx @@ -21,7 +21,10 @@ import { savedObjectReadOnlyErrorMessage, CaseCallOut } from '../components/call export const CaseDetailsPage = React.memo(() => { const history = useHistory(); const userPermissions = useGetUserSavedObjectPermissions(); - const { detailName: caseId } = useParams<{ detailName?: string }>(); + const { detailName: caseId, subCaseId } = useParams<{ + detailName?: string; + subCaseId?: string; + }>(); const search = useGetUrlSearch(navTabs.case); if (userPermissions != null && !userPermissions.read) { @@ -38,7 +41,11 @@ export const CaseDetailsPage = React.memo(() => { messages={[{ ...savedObjectReadOnlyErrorMessage }]} /> )} - + diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 32c94e593665f8..314bdc9bfd117f 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -15,7 +15,9 @@ import { ConfigureCasesPage } from './configure_cases'; const casesPagePath = ''; const caseDetailsPagePath = `${casesPagePath}/:detailName`; -const caseDetailsPagePathWithCommentId = `${casesPagePath}/:detailName/:commentId`; +const subCaseDetailsPagePath = `${caseDetailsPagePath}/sub-cases/:subCaseId`; +const caseDetailsPagePathWithCommentId = `${caseDetailsPagePath}/:commentId`; +const subCaseDetailsPagePathWithCommentId = `${subCaseDetailsPagePath}/:commentId`; const createCasePagePath = `${casesPagePath}/create`; const configureCasesPagePath = `${casesPagePath}/configure`; @@ -27,7 +29,13 @@ const CaseContainerComponent: React.FC = () => ( - + + + + + + + diff --git a/x-pack/plugins/security_solution/public/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts index 156cb91994468e..caaa1f6e248ea6 100644 --- a/x-pack/plugins/security_solution/public/cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/translations.ts @@ -131,6 +131,10 @@ export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView. defaultMessage: 'Reopen case', }); +export const OPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.openCase', { + defaultMessage: 'Open case', +}); + export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { defaultMessage: 'Case name', }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx index e388b485ba89c1..af76a79f0e330a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx @@ -20,7 +20,6 @@ import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_p import { useAddOrUpdateException } from '../use_add_exception'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; -import { Ecs } from '../../../../../common/ecs'; import * as builder from '../builder'; import * as helpers from '../helpers'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; @@ -31,6 +30,7 @@ import { getRulesSchemaMock, } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; +import { AlertData } from '../types'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); @@ -157,8 +157,11 @@ describe('When the add exception modal is opened', () => { describe('when there is alert data passed to an endpoint list exception', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; - + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { describe('when there is alert data passed to a detection list exception', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; - + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { describe('when there is an exception being created on a sequence eql rule type', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; (useRuleAsync as jest.Mock).mockImplementation(() => ({ rule: { ...getRulesEqlSchemaMock(), @@ -270,6 +275,11 @@ describe('When the add exception modal is opened', () => { 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', }, })); + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { }, }, ]); - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> void; onConfirm: (didCloseAlert: boolean, didBulkCloseAlert: boolean) => void; @@ -103,6 +109,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ruleIndices, exceptionListType, alertData, + isAlertDataLoading, onCancel, onConfirm, onRuleChange, @@ -239,7 +246,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ } else { return []; } - }, [alertData, exceptionListType, ruleExceptionList, ruleName]); + }, [exceptionListType, ruleExceptionList, ruleName, alertData]); useEffect((): void => { if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { @@ -372,6 +379,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ (isLoadingExceptionList || isIndexPatternLoading || isSignalIndexLoading || + isAlertDataLoading || isSignalIndexPatternLoading) && ( )} @@ -382,6 +390,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ !isIndexPatternLoading && !isRuleLoading && !mlJobLoading && + !isAlertDataLoading && ruleExceptionList && ( <> @@ -421,7 +430,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ - {alertData !== undefined && alertStatus !== 'closed' && ( + {alertData != null && alertStatus !== 'closed' && ( { }); describe('getPrepopulatedItem', () => { - test('it returns prepopulated items', () => { - const prepopulatedItem = getPrepopulatedItem({ + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'some-file-path', hash: { sha256: 'some-hash' } }, + }; + test('it returns prepopulated fields with empty values', () => { + const prepopulatedItem = getPrepopulatedEndpointException({ listId: 'some_id', ruleName: 'my rule', codeSignature: { subjectName: '', trusted: '' }, - filePath: '', - sha256Hash: '', eventCode: '', + alertEcsData: { ...alertDataMock, file: { path: '', hash: { sha256: '' } } }, }); expect(prepopulatedItem.entries).toEqual([ @@ -660,14 +665,13 @@ describe('Exception helpers', () => { ]); }); - test('it returns prepopulated items with values', () => { - const prepopulatedItem = getPrepopulatedItem({ + test('it returns prepopulated items with actual values', () => { + const prepopulatedItem = getPrepopulatedEndpointException({ listId: 'some_id', ruleName: 'my rule', codeSignature: { subjectName: 'someSubjectName', trusted: 'false' }, - filePath: 'some-file-path', - sha256Hash: 'some-hash', eventCode: 'some-event-code', + alertEcsData: alertDataMock, }); expect(prepopulatedItem.entries).toEqual([ @@ -696,15 +700,15 @@ describe('Exception helpers', () => { }); }); - describe('getCodeSignatureValue', () => { + describe('getFileCodeSignature', () => { test('it works when file.Ext.code_signature is an object', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { code_signature: { - subject_name: ['some_subject'], - trusted: ['false'], + subject_name: 'some_subject', + trusted: 'false', }, }, }, @@ -714,13 +718,13 @@ describe('Exception helpers', () => { }); test('it works when file.Ext.code_signature is nested type', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { code_signature: [ - { subject_name: ['some_subject'], trusted: ['false'] }, - { subject_name: ['some_subject_2'], trusted: ['true'] }, + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, ], }, }, @@ -736,11 +740,11 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures values are empty', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { - code_signature: { subject_name: [], trusted: [] }, + code_signature: { subject_name: '', trusted: '' }, }, }, }); @@ -749,7 +753,7 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures is empty array', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { @@ -762,7 +766,81 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures does not exist', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ + _id: '123', + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + }); + + describe('getProcessCodeSignature', () => { + test('it works when file.Ext.code_signature is an object', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: { + subject_name: 'some_subject', + trusted: 'false', + }, + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: 'some_subject', trusted: 'false' }]); + }); + + test('it works when file.Ext.code_signature is nested type', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: [ + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, + ], + }, + }, + }); + + expect(codeSignatures).toEqual([ + { subjectName: 'some_subject', trusted: 'false' }, + { + subjectName: 'some_subject_2', + trusted: 'true', + }, + ]); + }); + + test('it returns default when file.Ext.code_signatures values are empty', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: { subject_name: '', trusted: '' }, + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + + test('it returns default when file.Ext.code_signatures is empty array', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: [], + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + + test('it returns default when file.Ext.code_signatures does not exist', () => { + const codeSignatures = getProcessCodeSignature({ _id: '123', }); @@ -771,23 +849,23 @@ describe('Exception helpers', () => { }); describe('defaultEndpointExceptionItems', () => { - test('it should return pre-populated items', () => { + test('it should return pre-populated Endpoint items for non-specified event code', () => { const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { _id: '123', file: { Ext: { code_signature: [ - { subject_name: ['some_subject'], trusted: ['false'] }, - { subject_name: ['some_subject_2'], trusted: ['true'] }, + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, ], }, - path: ['some file path'], + path: 'some file path', hash: { - sha256: ['some hash'], + sha256: 'some hash', }, }, event: { - code: ['some event code'], + code: 'some event code', }, }); @@ -838,5 +916,88 @@ describe('Exception helpers', () => { { field: 'event.code', operator: 'included', type: 'match', value: 'some event code' }, ]); }); + + test('it should return pre-populated ransomware items for event code `ransomware`', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + process: { + Ext: { + code_signature: [ + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, + ], + }, + executable: 'some file path', + hash: { + sha256: 'some hash', + }, + }, + Ransomware: { + feature: 'some ransomware feature', + }, + event: { + code: 'ransomware', + }, + }); + + expect(defaultItems[0].entries).toEqual([ + { + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: 'some_subject', + }, + { field: 'trusted', operator: 'included', type: 'match', value: 'false' }, + ], + field: 'process.Ext.code_signature', + type: 'nested', + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: 'some file path', + }, + { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: 'some ransomware feature', + }, + { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + ]); + expect(defaultItems[1].entries).toEqual([ + { + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: 'some_subject_2', + }, + { field: 'trusted', operator: 'included', type: 'match', value: 'true' }, + ], + field: 'process.Ext.code_signature', + type: 'nested', + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: 'some file path', + }, + { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: 'some ransomware feature', + }, + { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + ]); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 1a66dd2f27cc2b..507fd51a90486b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -16,6 +16,7 @@ import { BuilderEntry, CreateExceptionListItemBuilderSchema, ExceptionsBuilderExceptionItem, + Flattened, } from './types'; import { EXCEPTION_OPERATORS, isOperator } from '../autocomplete/operators'; import { OperatorOption } from '../autocomplete/types'; @@ -264,6 +265,16 @@ export const enrichNewExceptionItemsWithComments = ( }); }; +export const buildGetAlertByIdQuery = (id: string | undefined) => ({ + query: { + match: { + _id: { + query: id || '', + }, + }, + }, +}); + /** * Adds new and existing comments to exceptionItem * @param exceptionItem existing ExceptionItem @@ -358,31 +369,50 @@ export const entryHasListType = ( * Returns the value for `file.Ext.code_signature` which * can be an object or array of objects */ -export const getCodeSignatureValue = ( - alertData: Ecs +export const getFileCodeSignature = ( + alertData: Flattened ): Array<{ subjectName: string; trusted: string }> => { const { file } = alertData; const codeSignature = file && file.Ext && file.Ext.code_signature; - // Pre 7.10 file.Ext.code_signature was mistakenly populated as - // a single object with subject_name and trusted. + return getCodeSignatureValue(codeSignature); +}; + +/** + * Returns the value for `process.Ext.code_signature` which + * can be an object or array of objects + */ +export const getProcessCodeSignature = ( + alertData: Flattened +): Array<{ subjectName: string; trusted: string }> => { + const { process } = alertData; + const codeSignature = process && process.Ext && process.Ext.code_signature; + return getCodeSignatureValue(codeSignature); +}; + +/** + * Pre 7.10 `Ext.code_signature` fields were mistakenly populated as + * a single object with subject_name and trusted. + */ +export const getCodeSignatureValue = ( + codeSignature: Flattened | Flattened | undefined +): Array<{ subjectName: string; trusted: string }> => { if (Array.isArray(codeSignature) && codeSignature.length > 0) { - return codeSignature.map((signature) => ({ - subjectName: (signature.subject_name && signature.subject_name[0]) ?? '', - trusted: (signature.trusted && signature.trusted[0]) ?? '', - })); + return codeSignature.map((signature) => { + return { + subjectName: signature.subject_name ?? '', + trusted: signature.trusted ?? '', + }; + }); } else { - const signature: CodeSignature | undefined = !Array.isArray(codeSignature) + const signature: Flattened | undefined = !Array.isArray(codeSignature) ? codeSignature : undefined; - const subjectName: string | undefined = - signature && signature.subject_name && signature.subject_name[0]; - const trusted: string | undefined = signature && signature.trusted && signature.trusted[0]; return [ { - subjectName: subjectName ?? '', - trusted: trusted ?? '', + subjectName: signature?.subject_name ?? '', + trusted: signature?.trusted ?? '', }, ]; } @@ -391,23 +421,24 @@ export const getCodeSignatureValue = ( /** * Returns the default values from the alert data to autofill new endpoint exceptions */ -export const getPrepopulatedItem = ({ +export const getPrepopulatedEndpointException = ({ listId, ruleName, codeSignature, - filePath, - sha256Hash, eventCode, listNamespace = 'agnostic', + alertEcsData, }: { listId: string; listNamespace?: NamespaceType; ruleName: string; codeSignature: { subjectName: string; trusted: string }; - filePath: string; - sha256Hash: string; eventCode: string; + alertEcsData: Flattened; }): ExceptionsBuilderExceptionItem => { + const { file } = alertEcsData; + const filePath = file?.path ?? ''; + const sha256Hash = file?.hash?.sha256 ?? ''; return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), entries: [ @@ -451,6 +482,77 @@ export const getPrepopulatedItem = ({ }; }; +/** + * Returns the default values from the alert data to autofill new endpoint exceptions + */ +export const getPrepopulatedRansomwareException = ({ + listId, + ruleName, + codeSignature, + eventCode, + listNamespace = 'agnostic', + alertEcsData, +}: { + listId: string; + listNamespace?: NamespaceType; + ruleName: string; + codeSignature: { subjectName: string; trusted: string }; + eventCode: string; + alertEcsData: Flattened; +}): ExceptionsBuilderExceptionItem => { + const { process, Ransomware } = alertEcsData; + const sha256Hash = process?.hash?.sha256 ?? ''; + const executable = process?.executable ?? ''; + const ransomwareFeature = Ransomware?.feature ?? ''; + return { + ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), + entries: [ + { + field: 'process.Ext.code_signature', + type: 'nested', + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.subjectName : '', + }, + { + field: 'trusted', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.trusted : '', + }, + ], + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: executable ?? '', + }, + { + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: sha256Hash ?? '', + }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: ransomwareFeature ?? '', + }, + { + field: 'event.code', + operator: 'included', + type: 'match', + value: eventCode ?? '', + }, + ], + }; +}; + /** * Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping */ @@ -487,18 +589,31 @@ export const entryHasNonEcsType = ( export const defaultEndpointExceptionItems = ( listId: string, ruleName: string, - alertEcsData: Ecs + alertEcsData: Flattened ): ExceptionsBuilderExceptionItem[] => { - const { file, event: alertEvent } = alertEcsData; + const { event: alertEvent } = alertEcsData; + const eventCode = alertEvent?.code ?? ''; + + if (eventCode === 'ransomware') { + return getProcessCodeSignature(alertEcsData).map((codeSignature) => + getPrepopulatedRansomwareException({ + listId, + ruleName, + eventCode, + codeSignature, + alertEcsData, + }) + ); + } - return getCodeSignatureValue(alertEcsData).map((codeSignature) => - getPrepopulatedItem({ + // By default return the standard prepopulated Endpoint Exception fields + return getFileCodeSignature(alertEcsData).map((codeSignature) => + getPrepopulatedEndpointException({ listId, ruleName, - filePath: file && file.path ? file.path[0] : '', - sha256Hash: file && file.hash && file.hash.sha256 ? file.hash.sha256[0] : '', - eventCode: alertEvent && alertEvent.code ? alertEvent.code[0] : '', + eventCode, codeSignature, + alertEcsData, }) ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts index 3593fe3d054910..6108a21ce5624a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts @@ -6,6 +6,8 @@ */ import { ReactNode } from 'react'; +import { Ecs } from '../../../../common/ecs'; +import { CodeSignature } from '../../../../common/ecs/file'; import { IFieldType } from '../../../../../../../src/plugins/data/common'; import { OperatorOption } from '../autocomplete/types'; import { @@ -104,3 +106,32 @@ export type CreateExceptionListItemBuilderSchema = Omit< export type ExceptionsBuilderExceptionItem = | ExceptionListItemBuilderSchema | CreateExceptionListItemBuilderSchema; + +export interface FlattenedCodeSignature { + subject_name: string; + trusted: string; +} + +export type Flattened = { + [K in keyof T]: T[K] extends infer AliasType + ? AliasType extends CodeSignature[] + ? FlattenedCodeSignature[] + : AliasType extends Array + ? rawType + : AliasType extends object + ? Flattened + : AliasType + : never; +}; + +export type AlertData = { + '@timestamp': string; +} & Flattened; + +export interface EcsHit { + _id: string; + _index: string; + _source: { + '@timestamp': string; + } & Omit, '_id' | '_index'>; +} diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx index 7a7da6f89306cf..82d8aac904e9b3 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx @@ -9,19 +9,43 @@ import { appendSearch } from './helpers'; export const getCaseUrl = (search?: string | null) => `${appendSearch(search ?? undefined)}`; -export const getCaseDetailsUrl = ({ id, search }: { id: string; search?: string | null }) => - `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`; +export const getCaseDetailsUrl = ({ + id, + search, + subCaseId, +}: { + id: string; + search?: string | null; + subCaseId?: string; +}) => { + if (subCaseId) { + return `/${encodeURIComponent(id)}/sub-cases/${encodeURIComponent(subCaseId)}${appendSearch( + search ?? undefined + )}`; + } + return `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`; +}; export const getCaseDetailsUrlWithCommentId = ({ id, commentId, search, + subCaseId, }: { id: string; commentId: string; search?: string | null; -}) => - `/${encodeURIComponent(id)}/${encodeURIComponent(commentId)}${appendSearch(search ?? undefined)}`; + subCaseId?: string; +}) => { + if (subCaseId) { + return `/${encodeURIComponent(id)}/sub-cases/${encodeURIComponent( + subCaseId + )}/${encodeURIComponent(commentId)}${appendSearch(search ?? undefined)}`; + } + return `/${encodeURIComponent(id)}/${encodeURIComponent(commentId)}${appendSearch( + search ?? undefined + )}`; +}; export const getCreateCaseUrl = (search?: string | null) => `/create${appendSearch(search ?? undefined)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 6b4148db2b1eeb..8e2f57a1a597c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -164,24 +164,25 @@ export const NetworkDetailsLink = React.memo(NetworkDetailsLinkComponent); const CaseDetailsLinkComponent: React.FC<{ children?: React.ReactNode; detailName: string; + subCaseId?: string; title?: string; -}> = ({ children, detailName, title }) => { +}> = ({ children, detailName, subCaseId, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const { navigateToApp } = useKibana().services.application; const goToCaseDetails = useCallback( (ev) => { ev.preventDefault(); navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { - path: getCaseDetailsUrl({ id: detailName, search }), + path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, - [detailName, navigateToApp, search] + [detailName, navigateToApp, search, subCaseId] ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index 0da881d2c6ea47..b993bcda56b8ed 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -74,7 +74,14 @@ export interface MatrixHistogramQueryProps { stackByField: string; startDate: string; histogramType: MatrixHistogramType; - threshold?: { field: string | undefined; value: number } | undefined; + threshold?: + | { + field: string | string[] | undefined; + value: number; + cardinality_field?: string | undefined; + cardinality_value?: number | undefined; + } + | undefined; skip?: boolean; isPtrIncluded?: boolean; } diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_control.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_control.test.tsx new file mode 100644 index 00000000000000..953f39fcf23726 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_control.test.tsx @@ -0,0 +1,42 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useControl, UseControlsReturn } from './use_control'; + +describe('useControl', () => { + it('init', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + expect(result.current.isControlOpen).toBe(false); + }); + + it('should open the control', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + + act(() => { + result.current.openControl(); + }); + + expect(result.current.isControlOpen).toBe(true); + }); + + it('should close the control', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + + act(() => { + result.current.openControl(); + }); + + expect(result.current.isControlOpen).toBe(true); + + act(() => { + result.current.closeControl(); + }); + + expect(result.current.isControlOpen).toBe(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_control.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_control.tsx new file mode 100644 index 00000000000000..37d7f4ff79986f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_control.tsx @@ -0,0 +1,22 @@ +/* + * 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 { useState, useCallback } from 'react'; + +export interface UseControlsReturn { + isControlOpen: boolean; + openControl: () => void; + closeControl: () => void; +} + +export const useControl = (): UseControlsReturn => { + const [isControlOpen, setIsControlOpen] = useState(false); + const openControl = useCallback(() => setIsControlOpen(true), []); + const closeControl = useCallback(() => setIsControlOpen(false), []); + + return { isControlOpen, openControl, closeControl }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 143c39daace66a..7d577659d66e20 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -421,7 +421,7 @@ describe('alert actions', () => { ...mockEcsDataWithAlert, timestamp: '2020-03-20T17:59:46.349Z', }; - const result = determineToAndFrom({ ecsData: ecsDataMock }); + const result = determineToAndFrom({ ecs: ecsDataMock }); expect(result.from).toEqual('2020-03-20T17:54:46.349Z'); expect(result.to).toEqual('2020-03-20T17:59:46.349Z'); @@ -431,7 +431,7 @@ describe('alert actions', () => { const { timestamp, ...ecsDataMock } = { ...mockEcsDataWithAlert, }; - const result = determineToAndFrom({ ecsData: ecsDataMock }); + const result = determineToAndFrom({ ecs: ecsDataMock }); expect(result.from).toEqual('2020-03-01T17:54:46.349Z'); expect(result.to).toEqual('2020-03-01T17:59:46.349Z'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 9d38e2b369fa16..9f5ab7be8a117f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -8,7 +8,7 @@ /* eslint-disable complexity */ import dateMath from '@elastic/datemath'; -import { get, getOr, isEmpty, find } from 'lodash/fp'; +import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; @@ -38,7 +38,11 @@ import { replaceTemplateFieldFromDataProviders, } from './helpers'; import { KueryFilterQueryKind } from '../../../common/store'; -import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { + DataProvider, + QueryOperator, +} from '../../../timelines/components/timeline/data_providers/data_provider'; +import { esFilters } from '../../../../../../../src/plugins/data/public'; export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { @@ -46,7 +50,7 @@ export const getUpdateAlertsQuery = (eventIds: Readonly) => { bool: { filter: { terms: { - _id: [...eventIds], + _id: eventIds, }, }, }, @@ -102,17 +106,32 @@ export const updateAlertStatusAction = async ({ } }; -export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { +export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { + if (Array.isArray(ecs)) { + const timestamps = ecs.reduce((acc, item) => { + if (item.timestamp != null) { + const dateTimestamp = new Date(item.timestamp); + if (!acc.includes(dateTimestamp.valueOf())) { + return [...acc, dateTimestamp.valueOf()]; + } + } + return acc; + }, []); + return { + from: new Date(Math.min(...timestamps)).toISOString(), + to: new Date(Math.max(...timestamps)).toISOString(), + }; + } + const ecsData = ecs as Ecs; const ellapsedTimeRule = moment.duration( moment().diff( - dateMath.parse(ecsData.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') + dateMath.parse(ecsData?.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') ) ); - - const from = moment(ecsData.timestamp ?? new Date()) + const from = moment(ecsData?.timestamp ?? new Date()) .subtract(ellapsedTimeRule) .toISOString(); - const to = moment(ecsData.timestamp ?? new Date()).toISOString(); + const to = moment(ecsData?.timestamp ?? new Date()).toISOString(); return { to, from }; }; @@ -128,37 +147,82 @@ const getFiltersFromRule = (filters: string[]): Filter[] => }, [] as Filter[]); export const getThresholdAggregationDataProvider = ( - ecsData: Ecs, + ecsData: Ecs | Ecs[], nonEcsData: TimelineNonEcsData[] ): DataProvider[] => { - const aggregationField = ecsData.signal?.rule?.threshold?.field!; - const aggregationValue = - get(aggregationField, ecsData) ?? find(['field', aggregationField], nonEcsData)?.value; - const dataProviderValue = Array.isArray(aggregationValue) - ? aggregationValue[0] - : aggregationValue; - - if (!dataProviderValue) { - return []; - } + const thresholdEcsData: Ecs[] = Array.isArray(ecsData) ? ecsData : [ecsData]; + return thresholdEcsData.reduce((outerAcc, thresholdData) => { + const threshold = thresholdData.signal?.rule?.threshold as string[]; - const aggregationFieldId = aggregationField.replace('.', '-'); + let aggField: string[] = []; + let thresholdResult: { + terms?: Array<{ + field?: string; + value: string; + }>; + count: number; + }; - return [ - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, - name: aggregationField, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: aggregationField, - value: dataProviderValue, - operator: ':', - }, - }, - ]; + try { + thresholdResult = JSON.parse((thresholdData.signal?.threshold_result as string[])[0]); + aggField = JSON.parse(threshold[0]).field; + } catch (err) { + thresholdResult = { + terms: [ + { + field: (thresholdData.rule?.threshold as { field: string }).field, + value: (thresholdData.signal?.threshold_result as { value: string }).value, + }, + ], + count: (thresholdData.signal?.threshold_result as { count: number }).count, + }; + } + + const aggregationFields = Array.isArray(aggField) ? aggField : [aggField]; + + return [ + ...outerAcc, + ...aggregationFields.reduce((acc, aggregationField, i) => { + const aggregationValue = (thresholdResult.terms ?? []).filter( + (term: { field?: string | undefined; value: string }) => term.field === aggregationField + )[0].value; + const dataProviderValue = Array.isArray(aggregationValue) + ? aggregationValue[0] + : aggregationValue; + + if (!dataProviderValue) { + return acc; + } + + const aggregationFieldId = aggregationField.replace('.', '-'); + const dataProviderPartial = { + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, + name: aggregationField, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: aggregationField, + value: dataProviderValue, + operator: ':' as QueryOperator, + }, + }; + + if (i === 0) { + return [ + ...acc, + { + ...dataProviderPartial, + and: [], + }, + ]; + } else { + acc[0].and.push(dataProviderPartial); + return acc; + } + }, []), + ]; + }, []); }; export const isEqlRuleWithGroupId = (ecsData: Ecs) => @@ -169,20 +233,134 @@ export const isEqlRuleWithGroupId = (ecsData: Ecs) => export const isThresholdRule = (ecsData: Ecs) => ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'threshold'; +export const buildAlertsKqlFilter = ( + key: '_id' | 'signal.group.id', + alertIds: string[] +): Filter[] => { + return [ + { + query: { + bool: { + filter: { + ids: { + values: alertIds, + }, + }, + }, + }, + meta: { + alias: 'Alert Ids', + negate: false, + disabled: false, + type: 'phrases', + key, + value: alertIds.join(), + params: alertIds, + }, + $state: { + store: esFilters.FilterStateStore.APP_STATE, + }, + }, + ]; +}; + +export const buildTimelineDataProviderOrFilter = ( + alertsIds: string[], + _id: string +): { filters: Filter[]; dataProviders: DataProvider[] } => { + if (!isEmpty(alertsIds)) { + return { + dataProviders: [], + filters: buildAlertsKqlFilter('_id', alertsIds), + }; + } + return { + filters: [], + dataProviders: [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${_id}`, + name: _id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: '_id', + value: _id, + operator: ':' as const, + }, + }, + ], + }; +}; + +export const buildEqlDataProviderOrFilter = ( + alertsIds: string[], + ecs: Ecs[] | Ecs +): { filters: Filter[]; dataProviders: DataProvider[] } => { + if (!isEmpty(alertsIds) && Array.isArray(ecs)) { + return { + dataProviders: [], + filters: buildAlertsKqlFilter( + 'signal.group.id', + ecs.reduce((acc, ecsData) => { + const signalGroupId = ecsData.signal?.group?.id?.length + ? ecsData.signal?.group?.id[0] + : 'unknown-signal-group-id'; + if (!acc.includes(signalGroupId)) { + return [...acc, signalGroupId]; + } + return acc; + }, []) + ), + }; + } else if (!Array.isArray(ecs)) { + const signalGroupId = ecs.signal?.group?.id?.length + ? ecs.signal?.group?.id[0] + : 'unknown-signal-group-id'; + return { + dataProviders: [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${signalGroupId}`, + name: ecs._id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: 'signal.group.id', + value: signalGroupId, + operator: ':' as const, + }, + }, + ], + filters: [], + }; + } + return { filters: [], dataProviders: [] }; +}; + export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, - ecsData, + ecsData: ecs, nonEcsData, updateTimelineIsLoading, searchStrategyClient, }: SendAlertToTimelineActionProps) => { + /* FUTURE DEVELOPER + * We are making an assumption here that if you have an array of ecs data they are all coming from the same rule + * but we still want to determine the filter for each alerts + */ + const ecsData: Ecs = Array.isArray(ecs) && ecs.length > 0 ? ecs[0] : (ecs as Ecs); + const alertIds = Array.isArray(ecs) ? ecs.map((d) => d._id) : []; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; - const { to, from } = determineToAndFrom({ ecsData }); + const { to, from } = determineToAndFrom({ ecs }); - if (!isEmpty(timelineId) && apolloClient != null) { + // For now we do not want to populate the template timeline if we have alertIds + if (!isEmpty(timelineId) && apolloClient != null && isEmpty(alertIds)) { try { updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ @@ -275,7 +453,7 @@ export const sendAlertToTimelineAction = async ({ ...timelineDefaults, description: `_id: ${ecsData._id}`, filters: getFiltersFromRule(ecsData.signal?.rule?.filters as string[]), - dataProviders: [...getThresholdAggregationDataProvider(ecsData, nonEcsData)], + dataProviders: getThresholdAggregationDataProvider(ecsData, nonEcsData), id: TimelineId.active, indexNames: [], dateRange: { @@ -301,36 +479,11 @@ export const sendAlertToTimelineAction = async ({ ruleNote: noteContent, }); } else { - let dataProviders = [ - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, - name: ecsData._id, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '_id', - value: ecsData._id, - operator: ':' as const, - }, - }, - ]; + let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) { - const signalGroupId = ecsData.signal?.group?.id?.length - ? ecsData.signal?.group?.id[0] - : 'unknown-signal-group-id'; - dataProviders = [ - { - ...dataProviders[0], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${signalGroupId}`, - queryMatch: { - field: 'signal.group.id', - value: signalGroupId, - operator: ':' as const, - }, - }, - ]; + const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecs); + dataProviders = tempEql.dataProviders; + filters = tempEql.filters; } return createTimeline({ @@ -346,6 +499,7 @@ export const sendAlertToTimelineAction = async ({ end: to, }, eventType: 'all', + filters, kqlQuery: { filterQuery: { kuery: { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 9236700c2b2e24..b2e5638ff120ee 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -18,6 +18,7 @@ import { import styled from 'styled-components'; import { getOr } from 'lodash/fp'; +import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { TimelineId } from '../../../../../common/types/timeline'; import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; @@ -29,7 +30,10 @@ import { FILTER_OPEN, FILTER_CLOSED, FILTER_IN_PROGRESS } from '../alerts_filter import { updateAlertStatusAction } from '../actions'; import { SetEventsDeletedProps, SetEventsLoadingProps } from '../types'; import { Ecs } from '../../../../../common/ecs'; -import { AddExceptionModal } from '../../../../common/components/exceptions/add_exception_modal'; +import { + AddExceptionModal, + AddExceptionModalProps, +} from '../../../../common/components/exceptions/add_exception_modal'; import * as i18nCommon from '../../../../common/translations'; import * as i18n from '../translations'; import { @@ -40,6 +44,9 @@ import { import { inputsModel } from '../../../../common/store'; import { useUserData } from '../../user_info'; import { ExceptionListType } from '../../../../../common/shared_imports'; +import { AlertData, EcsHit } from '../../../../common/components/exceptions/types'; +import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query'; +import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_signal_index'; interface AlertContextMenuProps { ariaLabel?: string; @@ -386,12 +393,12 @@ const AlertContextMenuComponent: React.FC = ({
{exceptionModalType != null && ruleId != null && ecsRowData != null && ( - & { + ecsData: Ecs; +}; + +/** + * This component exists to fetch needed data outside of the AddExceptionModal + * Due to the conditional nature of the modal and how we use the `ecsData` field, + * we cannot use the fetch hook within the modal component itself + */ +const AddExceptionModalWrapper: React.FC = ({ + ruleName, + ruleId, + ruleIndices, + exceptionListType, + ecsData, + onCancel, + onConfirm, + alertStatus, + onRuleChange, +}) => { + const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); + + const { loading: isLoadingAlertData, data } = useQueryAlerts( + buildGetAlertByIdQuery(ecsData?._id), + signalIndexName + ); + + const enrichedAlert: AlertData | undefined = useMemo(() => { + if (isLoadingAlertData === false) { + const hit = data?.hits.hits[0]; + if (!hit) { + return undefined; + } + const { _id, _index, _source } = hit; + return { ..._source, _id, _index }; + } + }, [data?.hits.hits, isLoadingAlertData]); + + const isLoading = isLoadingAlertData && isSignalIndexLoading; + + return ( + + ); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx index d6813fdef8e54e..2f0fee980c2181 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -24,14 +24,18 @@ import { } from '../translations'; interface InvestigateInTimelineActionProps { - ariaLabel?: string; - ecsRowData: Ecs; + ecsRowData: Ecs | Ecs[] | null; nonEcsRowData: TimelineNonEcsData[]; + ariaLabel?: string; + alertIds?: string[]; + fetchEcsAlertsData?: (alertIds?: string[]) => Promise; } const InvestigateInTimelineActionComponent: React.FC = ({ ariaLabel = ACTION_INVESTIGATE_IN_TIMELINE_ARIA_LABEL, + alertIds, ecsRowData, + fetchEcsAlertsData, nonEcsRowData, }) => { const { @@ -66,25 +70,42 @@ const InvestigateInTimelineActionComponent: React.FC - sendAlertToTimelineAction({ - apolloClient, - createTimeline, - ecsData: ecsRowData, - nonEcsData: nonEcsRowData, - searchStrategyClient, - updateTimelineIsLoading, - }), - [ - apolloClient, - createTimeline, - ecsRowData, - nonEcsRowData, - searchStrategyClient, - updateTimelineIsLoading, - ] - ); + const investigateInTimelineAlertClick = useCallback(async () => { + try { + if (ecsRowData != null) { + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: ecsRowData, + nonEcsData: nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + }); + } + if (ecsRowData == null && fetchEcsAlertsData) { + const alertsEcsData = await fetchEcsAlertsData(alertIds); + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: alertsEcsData, + nonEcsData: nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + }); + } + } catch { + // TODO show a toaster that something went wrong + } + }, [ + alertIds, + apolloClient, + createTimeline, + ecsRowData, + fetchEcsAlertsData, + nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + ]); return ( ; createTimeline: CreateTimeline; - ecsData: Ecs; + ecsData: Ecs | Ecs[]; nonEcsData: TimelineNonEcsData[]; updateTimelineIsLoading: UpdateTimelineLoading; searchStrategyClient: ISearchStart; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx index e954f961e73362..bb87242d9bf108 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx @@ -288,7 +288,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: 'agent.hostname', value: 200 }} + threshold={{ + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> @@ -330,7 +335,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: 'agent.hostname', value: 200 }} + threshold={{ + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> @@ -369,7 +379,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: undefined, value: 200 }} + threshold={{ + field: undefined, + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> @@ -396,7 +411,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: ' ', value: 200 }} + threshold={{ + field: ' ', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index 6a11dcf316de31..377259fc9b212a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -56,7 +56,14 @@ export const initialState: State = { showNonEqlHistogram: false, }; -export type Threshold = { field: string | undefined; value: number } | undefined; +export type Threshold = + | { + field: string | string[] | undefined; + value: number; + cardinality_field: string | undefined; + cardinality_value: number | undefined; + } + | undefined; interface PreviewQueryProps { dataTestSubj: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts index ff90978dbb53f0..d1a9e5c5f768f4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts @@ -334,7 +334,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to true if threshold field is defined and not empty string', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -347,7 +352,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to false if threshold field is not defined', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: undefined, value: 200 }, + threshold: { + field: undefined, + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -360,7 +370,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to false if threshold field is empty string', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: ' ', value: 200 }, + threshold: { + field: ' ', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -373,7 +388,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to false if ruleType is eql', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'eql', }); @@ -385,7 +405,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to true if ruleType is query', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'query', }); @@ -397,7 +422,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to true if ruleType is saved_query', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'saved_query', }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts index 84206b51272df0..2d301bf96122dd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts @@ -67,6 +67,7 @@ export type Action = type: 'setToFrom'; }; +/* eslint-disable-next-line complexity */ export const queryPreviewReducer = () => (state: State, action: Action): State => { switch (action.type) { case 'setQueryInfo': { @@ -131,7 +132,9 @@ export const queryPreviewReducer = () => (state: State, action: Action): State = const thresholdField = action.threshold != null && action.threshold.field != null && - action.threshold.field.trim() !== ''; + ((typeof action.threshold.field === 'string' && action.threshold.field.trim() !== '') || + (Array.isArray(action.threshold.field) && + action.threshold.field.every((field) => field.trim() !== ''))); const showNonEqlHist = action.ruleType === 'query' || action.ruleType === 'saved_query' || diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx deleted file mode 100644 index f3669e128ac6ed..00000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx +++ /dev/null @@ -1,19 +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 React from 'react'; -import { shallow } from 'enzyme'; - -import { RuleStatus } from './index'; - -describe('RuleStatus', () => { - it('renders loader correctly', () => { - const wrapper = shallow(); - - expect(wrapper.dive().find('[data-test-subj="rule-status-loader"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx index 677e6de0ff485d..42a6cb8bed1d7d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx @@ -5,115 +5,43 @@ * 2.0. */ -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiHealth, - EuiLoadingSpinner, - EuiText, -} from '@elastic/eui'; -import React, { memo, useCallback, useEffect, useState } from 'react'; -import deepEqual from 'fast-deep-equal'; +import { EuiFlexItem, EuiHealth, EuiText } from '@elastic/eui'; +import React, { memo } from 'react'; -import { - useRuleStatus, - RuleInfoStatus, - RuleStatusType, -} from '../../../containers/detection_engine/rules'; +import { RuleStatusType } from '../../../containers/detection_engine/rules'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { getStatusColor } from './helpers'; import * as i18n from './translations'; interface RuleStatusProps { - ruleId: string | null; - ruleEnabled?: boolean | null; + children: React.ReactNode | null | undefined; + statusDate: string | null | undefined; + status: RuleStatusType | null | undefined; } -const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) => { - const [loading, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); - const [myRuleEnabled, setMyRuleEnabled] = useState(ruleEnabled ?? null); - const [currentStatus, setCurrentStatus] = useState( - ruleStatus?.current_status ?? null - ); - - useEffect(() => { - if (myRuleEnabled !== ruleEnabled && fetchRuleStatus != null && ruleId != null) { - fetchRuleStatus(ruleId); - if (myRuleEnabled !== ruleEnabled) { - setMyRuleEnabled(ruleEnabled ?? null); - } - } - }, [fetchRuleStatus, myRuleEnabled, ruleId, ruleEnabled, setMyRuleEnabled]); - - useEffect(() => { - if (!deepEqual(currentStatus, ruleStatus?.current_status)) { - setCurrentStatus(ruleStatus?.current_status ?? null); - } - }, [currentStatus, ruleStatus, setCurrentStatus]); - - const handleRefresh = useCallback(() => { - if (fetchRuleStatus != null && ruleId != null) { - fetchRuleStatus(ruleId); - } - }, [fetchRuleStatus, ruleId]); - - const getStatus = useCallback((status: RuleStatusType | null | undefined) => { - if (status == null) { - return getEmptyTagValue(); - } else if (status != null && status === 'partial failure') { - // Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' - // On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status - // and this code will no longer be necessary - // TODO: remove this code in 8.0.0 - return 'warning'; - } - return status; - }, []); - +const RuleStatusComponent: React.FC = ({ children, statusDate, status }) => { return ( - + <> - {i18n.STATUS} - {':'} + + + {status ?? getEmptyTagValue()} + + - {loading && ( - - - - )} - {!loading && ( + {statusDate != null && status != null && ( <> - - - {getStatus(currentStatus?.status)} - - + <>{i18n.STATUS_AT} - {currentStatus?.status_date != null && currentStatus?.status != null && ( - <> - - <>{i18n.STATUS_AT} - - - - - - )} - - + + )} - + {children} + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 4f87f9332475b1..268ffe620ad4ea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -17,9 +17,8 @@ import styled from 'styled-components'; import React, { useMemo, useCallback, useState, useEffect } from 'react'; import * as i18n from '../../../pages/detection_engine/rules/translations'; -import { enableRules } from '../../../containers/detection_engine/rules'; +import { enableRules, RulesTableAction } from '../../../containers/detection_engine/rules'; import { enableRulesAction } from '../../../pages/detection_engine/rules/all/actions'; -import { Action } from '../../../pages/detection_engine/rules/all/reducer'; import { useStateToaster, displayErrorToast } from '../../../../common/components/toasters'; import { bucketRulesResponse } from '../../../pages/detection_engine/rules/all/helpers'; @@ -33,7 +32,7 @@ const StaticSwitch = styled(EuiSwitch)` StaticSwitch.displayName = 'StaticSwitch'; export interface RuleSwitchProps { - dispatch?: React.Dispatch; + dispatch?: React.Dispatch; id: string; enabled: boolean; isDisabled?: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts index f73b2ccfb02ae6..08feb5f2e51660 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts @@ -29,7 +29,6 @@ export const stepAboutDefaultValue: AboutStepRule = { license: '', ruleNameOverride: '', tags: [], - threatIndicatorPath: '', timestampOverride: '', threat: threatDefault, note: '', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 25295a823ea66e..60647bbf9f4a4b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -41,6 +41,7 @@ import { RiskScoreField } from '../risk_score_mapping'; import { AutocompleteField } from '../autocomplete_field'; import { useFetchIndex } from '../../../../common/containers/source'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; const CommonUseField = getUseField({ component: Field }); @@ -309,7 +310,7 @@ const StepAboutRuleComponent: FC = ({ euiFieldProps: { fullWidth: true, disabled: isLoading, - placeholder: 'threat.indicator', + placeholder: DEFAULT_INDICATOR_PATH, }, }} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 5613d9bc1b4580..4c7a34dbdf0807 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -82,6 +82,8 @@ const stepDefineDefaultValue: DefineStepRule = { threshold: { field: [], value: '200', + cardinality_field: [], + cardinality_value: '2', }, timeline: { id: null, @@ -150,17 +152,30 @@ const StepDefineRuleComponent: FC = ({ ruleType: formRuleType, queryBar: formQuery, threatIndex: formThreatIndex, - 'threshold.value': formThresholdValue, 'threshold.field': formThresholdField, + 'threshold.value': formThresholdValue, + 'threshold.cardinality_field': formThresholdCardinalityField, + 'threshold.cardinality_value': formThresholdCardinalityValue, }, ] = useFormData< DefineStepRule & { - 'threshold.value': number | undefined; 'threshold.field': string[] | undefined; + 'threshold.value': number | undefined; + 'threshold.cardinality_field': string[] | undefined; + 'threshold.cardinality_value': number | undefined; } >({ form, - watch: ['index', 'ruleType', 'queryBar', 'threshold.value', 'threshold.field', 'threatIndex'], + watch: [ + 'index', + 'ruleType', + 'queryBar', + 'threshold.field', + 'threshold.value', + 'threshold.cardinality_field', + 'threshold.cardinality_value', + 'threatIndex', + ], }); const [isQueryBarValid, setIsQueryBarValid] = useState(false); const index = formIndex || initialState.index; @@ -274,17 +289,32 @@ const StepDefineRuleComponent: FC = ({ }, []); const thresholdFormValue = useMemo((): Threshold | undefined => { - return formThresholdValue != null && formThresholdField != null - ? { value: formThresholdValue, field: formThresholdField[0] } + return formThresholdValue != null && + formThresholdField != null && + formThresholdCardinalityField != null && + formThresholdCardinalityValue != null + ? { + field: formThresholdField[0], + value: formThresholdValue, + cardinality_field: formThresholdCardinalityField[0], + cardinality_value: formThresholdCardinalityValue, + } : undefined; - }, [formThresholdField, formThresholdValue]); + }, [ + formThresholdField, + formThresholdValue, + formThresholdCardinalityField, + formThresholdCardinalityValue, + ]); const ThresholdInputChildren = useCallback( - ({ thresholdField, thresholdValue }) => ( + ({ thresholdField, thresholdValue, thresholdCardinalityField, thresholdCardinalityValue }) => ( ), [aggregatableFields] @@ -429,6 +459,12 @@ const StepDefineRuleComponent: FC = ({ thresholdValue: { path: 'threshold.value', }, + thresholdCardinalityField: { + path: 'threshold.cardinality_field', + }, + thresholdCardinalityValue: { + path: 'threshold.cardinality_value', + }, }} > {ThresholdInputChildren} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx index 18f5008ff05f7c..a5352ede83d51d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx @@ -197,13 +197,13 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldLabel', { - defaultMessage: 'Field', + defaultMessage: 'Group by', } ), helpText: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldHelpText', { - defaultMessage: 'Select a field to group results by', + defaultMessage: "Select fields to group by. Fields are joined together with 'AND'", } ), }, @@ -239,6 +239,53 @@ export const schema: FormSchema = { }, ], }, + cardinality_field: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdCardinalityFieldLabel', + { + defaultMessage: 'Count', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldCardinalityFieldHelpText', + { + defaultMessage: 'Select a field to check cardinality', + } + ), + }, + cardinality_value: { + type: FIELD_TYPES.NUMBER, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdCardinalityValueFieldLabel', + { + defaultMessage: 'Unique values', + } + ), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = isThresholdRule(formData.ruleType); + if (!needsValidation) { + return; + } + return fieldValidators.numberGreaterThanField({ + than: 1, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.thresholdValueFieldData.numberGreaterThanOrEqualOneErrorMessage', + { + defaultMessage: 'Value must be greater than or equal to one.', + } + ), + allowEquality: true, + })(...args); + }, + }, + ], + }, }, threatIndex: { type: FIELD_TYPES.COMBO_BOX, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx index d473a83a9e35e0..287c99dce3e60a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx @@ -19,11 +19,15 @@ const FIELD_COMBO_BOX_WIDTH = 410; export interface FieldValueThreshold { field: string[]; value: string; + cardinality_field: string[]; + cardinality_value: string; } interface ThresholdInputProps { thresholdField: FieldHook; thresholdValue: FieldHook; + thresholdCardinalityField: FieldHook; + thresholdCardinalityValue: FieldHook; browserFields: BrowserFields; } @@ -33,16 +37,19 @@ const OperatorWrapper = styled(EuiFlexItem)` const fieldDescribedByIds = ['detectionEngineStepDefineRuleThresholdField']; const valueDescribedByIds = ['detectionEngineStepDefineRuleThresholdValue']; +const cardinalityFieldDescribedByIds = ['detectionEngineStepDefineRuleThresholdCardinalityField']; +const cardinalityValueDescribedByIds = ['detectionEngineStepDefineRuleThresholdCardinalityValue']; const ThresholdInputComponent: React.FC = ({ thresholdField, thresholdValue, browserFields, + thresholdCardinalityField, + thresholdCardinalityValue, }: ThresholdInputProps) => { const fieldEuiFieldProps = useMemo( () => ({ fullWidth: true, - singleSelection: { asPlainText: true }, noSuggestions: false, options: getCategorizedFieldNames(browserFields), placeholder: THRESHOLD_FIELD_PLACEHOLDER, @@ -51,29 +58,65 @@ const ThresholdInputComponent: React.FC = ({ }), [browserFields] ); + const cardinalityFieldEuiProps = useMemo( + () => ({ + fullWidth: true, + noSuggestions: false, + options: getCategorizedFieldNames(browserFields), + placeholder: THRESHOLD_FIELD_PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + singleSelection: { asPlainText: true }, + }), + [browserFields] + ); return ( - - - - - {'>='} - - - + + + + + + {'>='} + + + + + + + + + {'>='} + + + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx index 475160b7872723..8557e1082c1cb9 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import React, { SetStateAction, useEffect, useState } from 'react'; import { fetchQueryAlerts } from './api'; @@ -80,7 +81,9 @@ export const useQueryAlerts = ( } }; - fetchData(); + if (!isEmpty(query)) { + fetchData(); + } return () => { isSubscribed = false; abortCtrl.abort(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts index 751cde64bb87d6..8128eb045f759d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts @@ -10,6 +10,6 @@ export * from './use_update_rule'; export * from './use_create_rule'; export * from './types'; export * from './use_rule'; -export * from './use_rules'; +export * from './rules_table'; export * from './use_pre_packaged_rules'; export * from './use_rule_status'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts new file mode 100644 index 00000000000000..a05349fa4fa3a8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts @@ -0,0 +1,11 @@ +/* + * 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 './rules_table_facade'; +export * from './rules_table_reducer'; +export * from './use_rules'; +export * from './use_rules_table'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts new file mode 100644 index 00000000000000..77c327c9f7939b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts @@ -0,0 +1,84 @@ +/* + * 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 { Dispatch } from 'react'; +import { Rule, FilterOptions, PaginationOptions } from '../types'; +import { RulesTableAction, LoadingRuleAction } from './rules_table_reducer'; + +export interface RulesTableFacade { + setRules(newRules: Rule[], newPagination: Partial): void; + updateRules(rules: Rule[]): void; + updateOptions(filter: Partial, pagination: Partial): void; + actionStarted(actionType: LoadingRuleAction, ruleIds: string[]): void; + actionStopped(): void; + setShowIdleModal(show: boolean): void; + setLastRefreshDate(): void; + setAutoRefreshOn(on: boolean): void; +} + +export const createRulesTableFacade = (dispatch: Dispatch): RulesTableFacade => { + return { + setRules: (newRules: Rule[], newPagination: Partial) => { + dispatch({ + type: 'setRules', + rules: newRules, + pagination: newPagination, + }); + }, + + updateRules: (rules: Rule[]) => { + dispatch({ + type: 'updateRules', + rules, + }); + }, + + updateOptions: (filter: Partial, pagination: Partial) => { + dispatch({ + type: 'updateFilterOptions', + filterOptions: filter, + pagination, + }); + }, + + actionStarted: (actionType: LoadingRuleAction, ruleIds: string[]) => { + dispatch({ + type: 'loadingRuleIds', + actionType, + ids: ruleIds, + }); + }, + + actionStopped: () => { + dispatch({ + type: 'loadingRuleIds', + actionType: null, + ids: [], + }); + }, + + setShowIdleModal: (show: boolean) => { + dispatch({ + type: 'setShowIdleModal', + show, + }); + }, + + setLastRefreshDate: () => { + dispatch({ + type: 'setLastRefreshDate', + }); + }, + + setAutoRefreshOn: (on: boolean) => { + dispatch({ + type: 'setAutoRefreshOn', + on, + }); + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts similarity index 94% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts index 20e4e5f7473492..1a45c60dba58a4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts @@ -5,13 +5,17 @@ * 2.0. */ -import { FilterOptions, PaginationOptions } from '../../../../containers/detection_engine/rules'; +import { mockRule } from '../../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { FilterOptions, PaginationOptions } from '../types'; +import { RulesTableAction, RulesTableState, createRulesTableReducer } from './rules_table_reducer'; -import { Action, State, allRulesReducer } from './reducer'; -import { mockRule } from './__mocks__/mock'; - -const initialState: State = { - exportRuleIds: [], +const initialState: RulesTableState = { + rules: [], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, filterOptions: { filter: '', sortField: 'enabled', @@ -20,29 +24,24 @@ const initialState: State = { showCustomRules: false, showElasticRules: false, }, - loadingRuleIds: [], loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 0, - }, - rules: [], + loadingRuleIds: [], selectedRuleIds: [], + exportRuleIds: [], lastUpdated: 0, - showIdleModal: false, isRefreshOn: false, + showIdleModal: false, }; describe('allRulesReducer', () => { - let reducer: (state: State, action: Action) => State; + let reducer: (state: RulesTableState, action: RulesTableAction) => RulesTableState; beforeEach(() => { jest.useFakeTimers(); jest .spyOn(global.Date, 'now') .mockImplementationOnce(() => new Date('2020-10-31T11:01:58.135Z').valueOf()); - reducer = allRulesReducer({ current: undefined }); + reducer = createRulesTableReducer({ current: undefined }); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts new file mode 100644 index 00000000000000..edcf4f6395d895 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts @@ -0,0 +1,165 @@ +/* + * 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 React from 'react'; +import { EuiBasicTable } from '@elastic/eui'; +import { FilterOptions, PaginationOptions, Rule } from '../types'; + +export type LoadingRuleAction = + | 'load' + | 'duplicate' + | 'enable' + | 'disable' + | 'export' + | 'delete' + | null; + +export interface RulesTableState { + rules: Rule[]; + pagination: PaginationOptions; + filterOptions: FilterOptions; + loadingRulesAction: LoadingRuleAction; + loadingRuleIds: string[]; + selectedRuleIds: string[]; + exportRuleIds: string[]; + lastUpdated: number; + isRefreshOn: boolean; + showIdleModal: boolean; +} + +export type RulesTableAction = + | { type: 'setRules'; rules: Rule[]; pagination: Partial } + | { type: 'updateRules'; rules: Rule[] } + | { + type: 'updateFilterOptions'; + filterOptions: Partial; + pagination: Partial; + } + | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } + | { type: 'selectedRuleIds'; ids: string[] } + | { type: 'exportRuleIds'; ids: string[] } + | { type: 'setLastRefreshDate' } + | { type: 'setAutoRefreshOn'; on: boolean } + | { type: 'setShowIdleModal'; show: boolean } + | { type: 'failure' }; + +export const createRulesTableReducer = ( + tableRef: React.MutableRefObject | undefined> +) => { + const rulesTableReducer = (state: RulesTableState, action: RulesTableAction): RulesTableState => { + switch (action.type) { + case 'setRules': { + if ( + tableRef != null && + tableRef.current != null && + tableRef.current.changeSelection != null + ) { + // for future devs: eui basic table is not giving us a prop to set the value, so + // we are using the ref in setTimeout to reset on the next loop so that we + // do not get a warning telling us we are trying to update during a render + window.setTimeout(() => tableRef?.current?.changeSelection([]), 0); + } + + return { + ...state, + rules: action.rules, + selectedRuleIds: [], + loadingRuleIds: [], + loadingRulesAction: null, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'updateRules': { + const ruleIds = state.rules.map((r) => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map((r) => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; + } + case 'updateFilterOptions': { + return { + ...state, + filterOptions: { + ...state.filterOptions, + ...action.filterOptions, + }, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'loadingRuleIds': { + return { + ...state, + loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], + loadingRulesAction: action.actionType, + }; + } + case 'selectedRuleIds': { + return { + ...state, + selectedRuleIds: action.ids, + }; + } + case 'exportRuleIds': { + return { + ...state, + loadingRuleIds: action.ids, + loadingRulesAction: 'export', + exportRuleIds: action.ids, + }; + } + case 'setLastRefreshDate': { + return { + ...state, + lastUpdated: Date.now(), + }; + } + case 'setAutoRefreshOn': { + return { + ...state, + isRefreshOn: action.on, + }; + } + case 'setShowIdleModal': { + return { + ...state, + showIdleModal: action.show, + isRefreshOn: !action.show, + }; + } + case 'failure': { + return { + ...state, + rules: [], + }; + } + default: { + return state; + } + } + }; + + return rulesTableReducer; +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx index 5d226e8152596e..4532d3427375b1 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx @@ -7,9 +7,9 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRules, UseRules, ReturnRules } from './use_rules'; -import * as api from './api'; +import * as api from '../api'; -jest.mock('./api'); +jest.mock('../api'); describe('useRules', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx index 02784333992be2..f3c90ae12ae33c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx @@ -7,10 +7,10 @@ import { useEffect, useState, useRef } from 'react'; -import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from './types'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; -import { fetchRules } from './api'; -import * as i18n from './translations'; +import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from '../types'; +import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import { fetchRules } from '../api'; +import * as i18n from '../translations'; export type ReturnRules = [boolean, FetchRulesResponse | null, () => Promise]; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts new file mode 100644 index 00000000000000..f31b2894301ba6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts @@ -0,0 +1,131 @@ +/* + * 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 { Dispatch, useMemo, useReducer, useEffect, useRef } from 'react'; +import { EuiBasicTable } from '@elastic/eui'; + +import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import * as i18n from '../translations'; + +import { fetchRules } from '../api'; +import { createRulesTableReducer, RulesTableState, RulesTableAction } from './rules_table_reducer'; +import { createRulesTableFacade, RulesTableFacade } from './rules_table_facade'; + +const INITIAL_SORT_FIELD = 'enabled'; + +const initialStateDefaults: RulesTableState = { + rules: [], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + filterOptions: { + filter: '', + sortField: INITIAL_SORT_FIELD, + sortOrder: 'desc', + tags: [], + showCustomRules: false, + showElasticRules: false, + }, + loadingRulesAction: null, + loadingRuleIds: [], + selectedRuleIds: [], + exportRuleIds: [], + lastUpdated: 0, + isRefreshOn: true, + showIdleModal: false, +}; + +export interface UseRulesTableParams { + tableRef: React.MutableRefObject | undefined>; + initialStateOverride?: Partial; +} + +export interface UseRulesTableReturn extends RulesTableFacade { + state: RulesTableState; + dispatch: Dispatch; + reFetchRules: () => Promise; +} + +export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn => { + const { tableRef, initialStateOverride } = params; + + const initialState: RulesTableState = { + ...initialStateDefaults, + lastUpdated: Date.now(), + ...initialStateOverride, + }; + + const reducer = useMemo(() => createRulesTableReducer(tableRef), [tableRef]); + const [state, dispatch] = useReducer(reducer, initialState); + const facade = useRef(createRulesTableFacade(dispatch)); + + const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); + const [, dispatchToaster] = useStateToaster(); + + const { pagination, filterOptions } = state; + const filterTags = filterOptions.tags.sort().join(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchData = async () => { + try { + facade.current.actionStarted('load', []); + + const fetchRulesResult = await fetchRules({ + filterOptions, + pagination, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + facade.current.setRules(fetchRulesResult.data, { + page: fetchRulesResult.page, + perPage: fetchRulesResult.perPage, + total: fetchRulesResult.total, + }); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + facade.current.setRules([], {}); + } + } + if (isSubscribed) { + facade.current.actionStopped(); + } + }; + + fetchData(); + reFetchRules.current = () => fetchData(); + + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + pagination.page, + pagination.perPage, + filterOptions.filter, + filterOptions.sortField, + filterOptions.sortOrder, + filterTags, + filterOptions.showCustomRules, + filterOptions.showElasticRules, + ]); + + return { + state, + dispatch, + ...facade.current, + reFetchRules: reFetchRules.current, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 9853b8c6d34bc4..8bdbb1a74c73a1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -141,8 +141,10 @@ export const mockRuleWithEverything = (id: string): Rule => ({ type: 'saved_query', threat: getThreatMock(), threshold: { - field: 'host.name', + field: ['host.name'], value: 50, + cardinality_field: ['process.name'], + cardinality_value: 2, }, throttle: 'no_actions', timestamp_override: 'event.ingested', @@ -192,6 +194,8 @@ export const mockDefineStepRule = (): DefineStepRule => ({ threshold: { field: [''], value: '100', + cardinality_field: [''], + cardinality_value: '2', }, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx index 44b0476501a5b9..3b1f9e620127d8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx @@ -13,6 +13,7 @@ import { duplicateRules, enableRules, Rule, + RulesTableAction, } from '../../../../containers/detection_engine/rules'; import { getEditRuleUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; @@ -27,7 +28,6 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t import * as i18n from '../translations'; import { bucketRulesResponse } from './helpers'; -import { Action } from './reducer'; export const editRuleAction = (rule: Rule, history: H.History) => { history.push(getEditRuleUrl(rule.id)); @@ -36,7 +36,7 @@ export const editRuleAction = (rule: Rule, history: H.History) => { export const duplicateRulesAction = async ( rules: Rule[], ruleIds: string[], - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { try { @@ -59,13 +59,16 @@ export const duplicateRulesAction = async ( } }; -export const exportRulesAction = (exportRuleId: string[], dispatch: React.Dispatch) => { +export const exportRulesAction = ( + exportRuleId: string[], + dispatch: React.Dispatch +) => { dispatch({ type: 'exportRuleIds', ids: exportRuleId }); }; export const deleteRulesAction = async ( ruleIds: string[], - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch, onRuleDeleted?: () => void ) => { @@ -96,7 +99,7 @@ export const deleteRulesAction = async ( export const enableRulesAction = async ( ids: string[], enabled: boolean, - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { const errorTitle = enabled diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx index ee19a0b5350754..d3e055a695d61b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx @@ -8,7 +8,7 @@ import { EuiContextMenuItem, EuiToolTip } from '@elastic/eui'; import React, { Dispatch } from 'react'; import * as i18n from '../translations'; -import { Action } from './reducer'; +import { RulesTableAction } from '../../../../containers/detection_engine/rules/rules_table'; import { deleteRulesAction, duplicateRulesAction, @@ -23,7 +23,7 @@ import { canEditRuleWithActions } from '../../../../../common/utils/privileges'; interface GetBatchItems { closePopover: () => void; - dispatch: Dispatch; + dispatch: Dispatch; dispatchToaster: Dispatch; hasMlPermissions: boolean; hasActionsPrivileges: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index d110f2d52b3c5e..d2488bd3d043cd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -34,14 +34,14 @@ import { editRuleAction, exportRulesAction, } from './actions'; -import { Action } from './reducer'; +import { RulesTableAction } from '../../../../containers/detection_engine/rules/rules_table'; import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip'; import { LinkAnchor } from '../../../../../common/components/links'; import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges'; import { TagsDisplay } from './tag_display'; export const getActions = ( - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch, history: H.History, reFetchRules: () => Promise, @@ -112,7 +112,7 @@ export type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType export type RulesStatusesColumns = EuiBasicTableColumn; type FormatUrl = (path: string) => string; interface GetColumns { - dispatch: React.Dispatch; + dispatch: React.Dispatch; dispatchToaster: Dispatch; formatUrl: FormatUrl; history: H.History; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index a784d23ab00153..7f9061b6abc83b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -13,7 +13,7 @@ import '../../../../../common/mock/match_media'; import '../../../../../common/mock/formatted_relative'; import { AllRules } from './index'; import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; -import { useRules, useRulesStatuses } from '../../../../containers/detection_engine/rules'; +import { useRulesTable, useRulesStatuses } from '../../../../containers/detection_engine/rules'; import { TestProviders } from '../../../../../common/mock'; import { createUseUiSetting$Mock } from '../../../../../common/lib/kibana/kibana_react.mock'; import { @@ -40,6 +40,7 @@ jest.mock('../../../../containers/detection_engine/rules'); const useKibanaMock = useKibana as jest.Mocked; const mockUseUiSetting$ = useUiSetting$ as jest.Mock; +const mockUseRulesTable = useRulesTable as jest.Mock; describe('AllRules', () => { const mockRefetchRulesData = jest.fn(); @@ -62,13 +63,9 @@ describe('AllRules', () => { : useUiSetting$Mock(key, defaultValue); }); - (useRules as jest.Mock).mockReturnValue([ - false, - { - page: 1, - perPage: 20, - total: 1, - data: [ + mockUseRulesTable.mockImplementation(({ initialStateOverride }) => { + const initialState = { + rules: [ { actions: [], created_at: '2020-02-14T19:49:28.178Z', @@ -101,9 +98,42 @@ describe('AllRules', () => { version: 1, }, ], - }, - mockRefetchRulesData, - ]); + pagination: { + page: 1, + perPage: 20, + total: 1, + }, + filterOptions: { + filter: '', + sortField: 'enabled', + sortOrder: 'desc', + tags: [], + showCustomRules: false, + showElasticRules: false, + }, + loadingRulesAction: null, + loadingRuleIds: [], + selectedRuleIds: [], + exportRuleIds: [], + lastUpdated: 0, + isRefreshOn: true, + showIdleModal: false, + }; + + return { + state: { ...initialState, ...initialStateOverride }, + dispatch: jest.fn(), + reFetchRules: mockRefetchRulesData, + setRules: jest.fn(), + updateRules: jest.fn(), + updateOptions: jest.fn(), + actionStarted: jest.fn(), + actionStopped: jest.fn(), + setShowIdleModal: jest.fn(), + setLastRefreshDate: jest.fn(), + setAutoRefreshOn: jest.fn(), + }; + }); (useRulesStatuses as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts deleted file mode 100644 index 60798f10a4c58f..00000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts +++ /dev/null @@ -1,156 +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 React from 'react'; -import { EuiBasicTable } from '@elastic/eui'; -import { - FilterOptions, - PaginationOptions, - Rule, -} from '../../../../containers/detection_engine/rules'; - -type LoadingRuleAction = 'duplicate' | 'enable' | 'disable' | 'export' | 'delete' | null; -export interface State { - exportRuleIds: string[]; - filterOptions: FilterOptions; - loadingRuleIds: string[]; - loadingRulesAction: LoadingRuleAction; - pagination: PaginationOptions; - rules: Rule[]; - selectedRuleIds: string[]; - lastUpdated: number; - showIdleModal: boolean; - isRefreshOn: boolean; -} - -export type Action = - | { type: 'exportRuleIds'; ids: string[] } - | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } - | { type: 'selectedRuleIds'; ids: string[] } - | { type: 'setRules'; rules: Rule[]; pagination: Partial } - | { type: 'updateRules'; rules: Rule[] } - | { - type: 'updateFilterOptions'; - filterOptions: Partial; - pagination: Partial; - } - | { type: 'failure' } - | { type: 'setLastRefreshDate' } - | { type: 'setShowIdleModal'; show: boolean } - | { type: 'setAutoRefreshOn'; on: boolean }; - -export const allRulesReducer = ( - tableRef: React.MutableRefObject | undefined> -) => (state: State, action: Action): State => { - switch (action.type) { - case 'exportRuleIds': { - return { - ...state, - loadingRuleIds: action.ids, - loadingRulesAction: 'export', - exportRuleIds: action.ids, - }; - } - case 'loadingRuleIds': { - return { - ...state, - loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], - loadingRulesAction: action.actionType, - }; - } - case 'selectedRuleIds': { - return { - ...state, - selectedRuleIds: action.ids, - }; - } - case 'setRules': { - if ( - tableRef != null && - tableRef.current != null && - tableRef.current.changeSelection != null - ) { - // for future devs: eui basic table is not giving us a prop to set the value, so - // we are using the ref in setTimeout to reset on the next loop so that we - // do not get a warning telling us we are trying to update during a render - window.setTimeout(() => tableRef?.current?.changeSelection([]), 0); - } - - return { - ...state, - rules: action.rules, - selectedRuleIds: [], - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - ...state.pagination, - ...action.pagination, - }, - }; - } - case 'updateRules': { - const ruleIds = state.rules.map((r) => r.id); - const updatedRules = action.rules.reduce((rules, updatedRule) => { - let newRules = rules; - if (ruleIds.includes(updatedRule.id)) { - newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); - } else { - newRules = [...newRules, updatedRule]; - } - return newRules; - }, state.rules); - const updatedRuleIds = action.rules.map((r) => r.id); - const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); - return { - ...state, - rules: updatedRules, - loadingRuleIds: newLoadingRuleIds, - loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, - }; - } - case 'updateFilterOptions': { - return { - ...state, - filterOptions: { - ...state.filterOptions, - ...action.filterOptions, - }, - pagination: { - ...state.pagination, - ...action.pagination, - }, - }; - } - case 'failure': { - return { - ...state, - rules: [], - }; - } - case 'setLastRefreshDate': { - return { - ...state, - lastUpdated: Date.now(), - }; - } - case 'setShowIdleModal': { - return { - ...state, - showIdleModal: action.show, - isRefreshOn: !action.show, - }; - } - case 'setAutoRefreshOn': { - return { - ...state, - isRefreshOn: action.on, - }; - } - default: - return state; - } -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 04bf3c544030a4..a40833d8d14acb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -12,21 +12,21 @@ import { EuiConfirmModal, EuiWindowEvent, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import uuid from 'uuid'; import { debounce } from 'lodash/fp'; import { History } from 'history'; import { - useRules, + useRulesTable, useRulesStatuses, CreatePreBuiltRules, FilterOptions, Rule, - PaginationOptions, exportRules, RulesSortingFields, } from '../../../../containers/detection_engine/rules'; + import { FormatUrl } from '../../../../../common/components/link_to'; import { HeaderSection } from '../../../../../common/components/header_section'; import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; @@ -42,7 +42,6 @@ import { EuiBasicTableOnChange } from '../types'; import { getBatchItems } from './batch_actions'; import { getColumns, getMonitoringColumns } from './columns'; import { showRulesTable } from './helpers'; -import { allRulesReducer, State } from './reducer'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; import { useMlCapabilities } from '../../../../../common/components/ml/hooks/use_ml_capabilities'; import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; @@ -54,29 +53,6 @@ import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/co import { AllRulesTabs } from '.'; const INITIAL_SORT_FIELD = 'enabled'; -const initialState: State = { - exportRuleIds: [], - filterOptions: { - filter: '', - sortField: INITIAL_SORT_FIELD, - sortOrder: 'desc', - tags: [], - showCustomRules: false, - showElasticRules: false, - }, - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 0, - }, - rules: [], - selectedRuleIds: [], - lastUpdated: 0, - showIdleModal: false, - isRefreshOn: true, -}; interface RulesTableProps { history: History; @@ -119,7 +95,7 @@ export const RulesTables = React.memo( selectedTab, }) => { const [initLoading, setInitLoading] = useState(true); - const tableRef = useRef(); + const { services: { application: { @@ -127,30 +103,47 @@ export const RulesTables = React.memo( }, }, } = useKibana(); + + const tableRef = useRef(); + const [defaultAutoRefreshSetting] = useUiSetting$<{ on: boolean; value: number; idleTimeout: number; }>(DEFAULT_RULES_TABLE_REFRESH_SETTING); - const [ - { - exportRuleIds, - filterOptions, - loadingRuleIds, - loadingRulesAction, - pagination, - rules, - selectedRuleIds, - lastUpdated, - showIdleModal, - isRefreshOn, + + const rulesTable = useRulesTable({ + tableRef, + initialStateOverride: { + isRefreshOn: defaultAutoRefreshSetting.on, }, - dispatch, - ] = useReducer(allRulesReducer(tableRef), { - ...initialState, - lastUpdated: Date.now(), - isRefreshOn: defaultAutoRefreshSetting.on, }); + + const { + exportRuleIds, + filterOptions, + loadingRuleIds, + loadingRulesAction, + pagination, + rules, + selectedRuleIds, + lastUpdated, + showIdleModal, + isRefreshOn, + } = rulesTable.state; + + const { + dispatch, + updateOptions, + actionStopped, + setShowIdleModal, + setLastRefreshDate, + setAutoRefreshOn, + reFetchRules, + } = rulesTable; + + const isLoadingRules = loadingRulesAction === 'load'; + const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const [, dispatchToaster] = useStateToaster(); const mlCapabilities = useMlCapabilities(); @@ -158,40 +151,6 @@ export const RulesTables = React.memo( // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); - const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { - dispatch({ - type: 'setRules', - rules: newRules, - pagination: newPagination, - }); - }, []); - - const setShowIdleModal = useCallback((show: boolean) => { - dispatch({ - type: 'setShowIdleModal', - show, - }); - }, []); - - const setLastRefreshDate = useCallback(() => { - dispatch({ - type: 'setLastRefreshDate', - }); - }, []); - - const setAutoRefreshOn = useCallback((on: boolean) => { - dispatch({ - type: 'setAutoRefreshOn', - on, - }); - }, []); - - const [isLoadingRules, , reFetchRules] = useRules({ - pagination, - filterOptions, - dispatchRulesInReducer: setRules, - }); - const sorting = useMemo( (): SortingType => ({ sort: { @@ -250,18 +209,24 @@ export const RulesTables = React.memo( [pagination] ); + const onFilterChangedCallback = useCallback( + (newFilter: Partial) => { + updateOptions(newFilter, { page: 1 }); + }, + [updateOptions] + ); + const tableOnChangeCallback = useCallback( ({ page, sort }: EuiBasicTableOnChange) => { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { + updateOptions( + { sortField: (sort?.field as RulesSortingFields) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types sortOrder: sort?.direction ?? 'desc', }, - pagination: { page: page.index + 1, perPage: page.size }, - }); + { page: page.index + 1, perPage: page.size } + ); }, - [dispatch] + [updateOptions] ); const rulesColumns = useMemo(() => { @@ -324,19 +289,9 @@ export const RulesTables = React.memo( onSelectionChange: (selected: Rule[]) => dispatch({ type: 'selectedRuleIds', ids: selected.map((r) => r.id) }), }), - [loadingRuleIds] + [loadingRuleIds, dispatch] ); - const onFilterChangedCallback = useCallback((newFilterOptions: Partial) => { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...newFilterOptions, - }, - pagination: { page: 1 }, - }); - }, []); - const isLoadingAnActionOnRule = useMemo(() => { if ( loadingRuleIds.length > 0 && @@ -412,7 +367,7 @@ export const RulesTables = React.memo( const handleGenericDownloaderSuccess = useCallback( (exportCount) => { - dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + actionStopped(); dispatchToaster({ type: 'addToaster', toast: { @@ -423,7 +378,7 @@ export const RulesTables = React.memo( }, }); }, - [dispatchToaster] + [actionStopped, dispatchToaster] ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 7c447214cfdebb..12e6d276c18d86 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -219,8 +219,10 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep saved_id: ruleFields.queryBar?.saved_id, ...(ruleType === 'threshold' && { threshold: { - field: ruleFields.threshold?.field[0] ?? '', + field: ruleFields.threshold?.field ?? [], value: parseInt(ruleFields.threshold?.value, 10) ?? 0, + cardinality_field: ruleFields.threshold.cardinality_field[0] ?? '', + cardinality_value: parseInt(ruleFields.threshold?.cardinality_value, 10) ?? 0, }, }), } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index c4dc9b62c74cd5..4e225917f076df 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -9,6 +9,7 @@ // TODO: Disabling complexity is temporary till this component is refactored as part of lists UI integration import { + EuiButtonIcon, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, @@ -24,6 +25,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useParams, useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { useDeepEqualSelector, @@ -41,7 +43,7 @@ import { } from '../../../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../../../common/components/search_bar'; import { WrapperPage } from '../../../../../common/components/wrapper_page'; -import { Rule } from '../../../../containers/detection_engine/rules'; +import { Rule, useRuleStatus, RuleInfoStatus } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; @@ -101,6 +103,7 @@ import { import * as detectionI18n from '../../translations'; import * as ruleI18n from '../translations'; +import * as statusI18n from '../../../../components/rules/rule_status/translations'; import * as i18n from './translations'; import { isTab } from '../../../../../common/components/accessibility/helpers'; import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout'; @@ -179,6 +182,15 @@ const RuleDetailsPageComponent = () => { const loading = userInfoLoading || listsConfigLoading; const { detailName: ruleId } = useParams<{ detailName: string }>(); const { rule: maybeRule, refresh: refreshRule, loading: ruleLoading } = useRuleAsync(ruleId); + const [loadingStatus, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); + const [currentStatus, setCurrentStatus] = useState( + ruleStatus?.current_status ?? null + ); + useEffect(() => { + if (!deepEqual(currentStatus, ruleStatus?.current_status)) { + setCurrentStatus(ruleStatus?.current_status ?? null); + } + }, [currentStatus, ruleStatus, setCurrentStatus]); const [rule, setRule] = useState(null); const isLoading = ruleLoading && rule == null; // This is used to re-trigger api rule status when user de/activate rule @@ -302,33 +314,65 @@ const RuleDetailsPageComponent = () => { ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); + + const handleRefresh = useCallback(() => { + if (fetchRuleStatus != null && ruleId != null) { + fetchRuleStatus(ruleId); + } + }, [fetchRuleStatus, ruleId]); + + const ruleStatusInfo = useMemo(() => { + return loadingStatus ? ( + + + + ) : ( + <> + + + + + ); + }, [currentStatus, loadingStatus, handleRefresh]); const ruleError = useMemo(() => { - if ( - rule?.status === 'failed' && + if (loadingStatus) { + return ( + + + + ); + } else if ( + currentStatus?.status === 'failed' && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_failure_at != null + currentStatus?.last_failure_at != null ) { return ( ); } else if ( - (rule?.status === 'warning' || rule?.status === 'partial failure') && + (currentStatus?.status === 'warning' || currentStatus?.status === 'partial failure') && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_success_at != null + currentStatus?.last_success_at != null ) { return ( ); } return null; - }, [rule, ruleDetailTab]); + }, [ruleDetailTab, currentStatus, loadingStatus]); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -500,7 +544,15 @@ const RuleDetailsPageComponent = () => { , ] : []), - , + <> + + + {statusI18n.STATUS} + {':'} + + {ruleStatusInfo} + + , ]} title={title} > diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index 111eb8a5594a8d..29d1512030e74f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -84,6 +84,8 @@ describe('rule helpers', () => { threshold: { field: ['host.name'], value: '50', + cardinality_field: ['process.name'], + cardinality_value: '2', }, threatIndex: [], threatMapping: [], @@ -116,7 +118,6 @@ describe('rule helpers', () => { severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: ['tag1', 'tag2'], threat: getThreatMock(), - threatIndicatorPath: '', timestampOverride: 'event.ingested', }; const scheduleRuleStepData = { from: '0s', interval: '5m' }; @@ -214,6 +215,8 @@ describe('rule helpers', () => { threshold: { field: [], value: '100', + cardinality_field: [], + cardinality_value: '0', }, threatIndex: [], threatMapping: [], @@ -256,6 +259,8 @@ describe('rule helpers', () => { threshold: { field: [], value: '100', + cardinality_field: [], + cardinality_value: '0', }, threatIndex: [], threatMapping: [], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index d37c2d9141f5d4..7c3930bb21d9a4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -99,8 +99,18 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ title: rule.timeline_title ?? null, }, threshold: { - field: rule.threshold?.field ? [rule.threshold.field] : [], + field: rule.threshold?.field + ? Array.isArray(rule.threshold.field) + ? rule.threshold.field + : [rule.threshold.field] + : [], value: `${rule.threshold?.value || 100}`, + cardinality_field: Array.isArray(rule.threshold?.cardinality_field) + ? rule.threshold!.cardinality_field + : rule.threshold?.cardinality_field != null + ? [rule.threshold!.cardinality_field] + : [], + cardinality_value: `${rule.threshold?.cardinality_value ?? 0}`, }, }); @@ -180,7 +190,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu }, falsePositives, threat: threat as Threats, - threatIndicatorPath: threatIndicatorPath ?? '', + threatIndicatorPath, }; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 94fdcc4069fc23..668ca556539ad0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -158,8 +158,10 @@ export interface DefineStepRuleJson { query?: string; language?: string; threshold?: { - field: string; + field: string[]; value: number; + cardinality_field: string; + cardinality_value: number; }; threat_query?: string; threat_mapping?: ThreatMapping; diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index b69614e73e7f60..540191ac63b6ca 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -31,6 +31,7 @@ export const FirstLastSeenHost = React.memo( docValueFields, hostName, indexNames, + order: type === FirstLastSeenHostType.FIRST_SEEN ? 'asc' : 'desc', }); const valueSeen = useMemo( () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 1394e2660a5aea..da574dfa4ea441 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -11,8 +11,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, - HostFirstLastSeenRequestOptions, HostFirstLastSeenStrategyResponse, + HostFirstLastSeenRequestOptions, } from '../../../../../common/search_strategy/security_solution'; import * as i18n from './translations'; @@ -30,17 +30,20 @@ export interface FirstLastSeenHostArgs { errorMessage: string | null; firstSeen?: string | null; lastSeen?: string | null; + order: 'asc' | 'desc' | null; } interface UseHostFirstLastSeen { docValueFields: DocValueFields[]; hostName: string; indexNames: string[]; + order: 'asc' | 'desc'; } export const useFirstLastSeenHost = ({ docValueFields, hostName, indexNames, + order, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { const { data, notifications } = useKibana().services; const abortCtrl = useRef(new AbortController()); @@ -51,12 +54,14 @@ export const useFirstLastSeenHost = ({ ] = useState({ defaultIndex: indexNames, docValueFields: docValueFields ?? [], - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, hostName, + order, }); const [firstLastSeenHostResponse, setFirstLastSeenHostResponse] = useState( { + order: null, firstSeen: null, lastSeen: null, errorMessage: null, diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts index 3e18c7a01c808e..1a007cd7f0f56d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts @@ -17,13 +17,13 @@ export const ENDPOINT_POLICY = i18n.translate( export const POLICY_STATUS = i18n.translate( 'xpack.securitySolution.host.details.endpoint.policyStatus', { - defaultMessage: 'Configuration Status', + defaultMessage: 'Policy Status', } ); export const SENSORVERSION = i18n.translate( 'xpack.securitySolution.host.details.endpoint.sensorversion', { - defaultMessage: 'Sensorversion', + defaultMessage: 'Sensor Version', } ); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 1b7b7059cbafce..6aaad4a1571919 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { pluck } from 'rxjs/operators'; import { PluginSetup, @@ -19,6 +19,7 @@ import { } from './types'; import { AppMountParameters, + AppUpdater, CoreSetup, CoreStart, PluginInitializerContext, @@ -46,6 +47,7 @@ import { } from '../common/constants'; import { SecurityPageName } from './app/types'; +import { registerSearchLinks, getSearchDeepLinksAndKeywords } from './app/search'; import { manageOldSiemRoutes } from './helpers'; import { OVERVIEW, @@ -73,8 +75,13 @@ export class Plugin implements IPlugin(); + private hostsUpdater$ = new Subject(); + private networkUpdater$ = new Subject(); + private caseUpdater$ = new Subject(); private storage = new Storage(localStorage); + private licensingSubscription: Subscription | null = null; /** * Lazily instantiated subPlugins. @@ -184,6 +191,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { detections: subPlugin } = await this.subPlugins(); @@ -206,6 +214,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { hosts: subPlugin } = await this.subPlugins(); @@ -227,6 +236,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { network: subPlugin } = await this.subPlugins(); @@ -248,6 +258,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { timelines: subPlugin } = await this.subPlugins(); @@ -269,6 +280,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { cases: subPlugin } = await this.subPlugins(); @@ -290,6 +302,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { management: managementSubPlugin } = await this.subPlugins(); @@ -356,11 +369,31 @@ export class Plugin implements IPlugin { + if (currentLicense.type !== undefined) { + registerSearchLinks(SecurityPageName.network, this.networkUpdater$, currentLicense.type); + registerSearchLinks( + SecurityPageName.detections, + this.detectionsUpdater$, + currentLicense.type + ); + registerSearchLinks(SecurityPageName.hosts, this.hostsUpdater$, currentLicense.type); + registerSearchLinks(SecurityPageName.case, this.caseUpdater$, currentLicense.type); + } + }); + } return {}; } public stop() { + if (this.licensingSubscription !== null) { + this.licensingSubscription.unsubscribe(); + } return {}; } diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx index a854884d2e340b..4a2056963ee7f1 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx @@ -130,18 +130,18 @@ describe('Resolver: data loading and resolution states', () => { }); }); - it('should display a resolver graph with 0 nodes', async () => { + it('should display a message informing the user why no nodes are present', async () => { await expect( simulator.map(() => ({ resolverGraphLoading: simulator.testSubject('resolver:graph:loading').length, resolverGraphError: simulator.testSubject('resolver:graph:error').length, - resolverTree: simulator.testSubject('resolver:graph').length, + resolverEmptyMessage: simulator.testSubject('resolver:no-process-events').length, resolverGraphNodes: simulator.testSubject('resolver:node').length, })) ).toYieldEqualTo({ resolverGraphLoading: 0, resolverGraphError: 0, - resolverTree: 1, + resolverEmptyMessage: 1, resolverGraphNodes: 0, }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx new file mode 100644 index 00000000000000..7159b0bda468ba --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx @@ -0,0 +1,55 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { EuiCodeBlock, EuiFlexGroup, EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; + +const StyledEuiCodeBlock = styled(EuiCodeBlock)` + align-self: flex-start; + display: inline-block; +`; + +const StyledEuiFlexGroup = styled(EuiFlexGroup)` + max-width: 600px; + margin: 60px auto 0; +`; + +export const ResolverNoProcessEvents = () => ( + + +

+ {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.title', { + defaultMessage: 'No Process Events Found', + })} +

+
+ + + {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.timeRange', { + defaultMessage: ` + The Analyze Event tool creates graphs based on process events. + If the analyzed event does not have an associated process in the current time range, + or stored in Elasticsearch within any time range, a graph will not be created. + You can check for associated processes by expanding your time range. + `, + })} + + + + {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.eventCategory', { + defaultMessage: `You may also add the below to your timeline query to check for process events. + If none are listed, a graph cannot be created from events found in that query.`, + })} + + + + {"event.category: 'process'"} + +
+); diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx index 6fbb81a5fe0da1..7b528bfa4d99d8 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ -import React, { useContext, useCallback } from 'react'; +import React, { useContext, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,6 +26,7 @@ import { ResolverProps, ResolverState } from '../types'; import { PanelRouter } from './panels'; import { useColors } from './use_colors'; import { useSyncSelectedNode } from './use_sync_selected_node'; +import { ResolverNoProcessEvents } from './resolver_no_process_events'; /** * The highest level connected Resolver component. Needs a `Provider` in its ancestry to work. @@ -96,6 +97,10 @@ export const ResolverWithoutProviders = React.memo( const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const colorMap = useColors(); + const noProcessEventsFound = useMemo(() => processNodePositions.size < 1, [ + processNodePositions, + ]); + return ( {isLoading ? ( @@ -112,6 +117,8 @@ export const ResolverWithoutProviders = React.memo( /> + ) : noProcessEventsFound ? ( + ) : ( <> = ({ isEventViewer, timelineId } globalFullScreen, ]); - let sourcereScope = SourcererScopeName.default; - if ([TimelineId.detectionsRulesDetailsPage, TimelineId.detectionsPage].includes(timelineId)) { - sourcereScope = SourcererScopeName.detections; - } else if (timelineId === TimelineId.active) { - sourcereScope = SourcererScopeName.timeline; - } + const existingIndexNamesSelector = useMemo( + () => sourcererSelectors.getAllExistingIndexNamesSelector(), + [] + ); + const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); - const { selectedPatterns } = useSourcererScope(sourcereScope); return ( = ({ isEventViewer, timelineId } diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index 4e101e29bb4841..8fce9a186bbd46 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiTitle } from '@elastic/eui'; import { HostDetailsLink } from '../../../../common/components/links'; @@ -23,14 +24,20 @@ interface ExpandableHostProps { hostName: string; } +const StyledTitle = styled.h4` + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; +`; + export const ExpandableHostDetailsTitle = ({ hostName }: ExpandableHostProps) => ( -

+ {i18n.translate('xpack.securitySolution.timeline.sidePanel.hostDetails.title', { defaultMessage: 'Host details', })} {`: ${hostName}`} -

+
); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index b12b575681acf2..19f6e2c9652f92 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -7,6 +7,7 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { FlowTarget } from '../../../../../common/search_strategy'; @@ -31,14 +32,20 @@ interface ExpandableNetworkProps { expandedNetwork: { ip: string; flowTarget: FlowTarget }; } +const StyledTitle = styled.h4` + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; +`; + export const ExpandableNetworkDetailsTitle = ({ ip }: { ip: string }) => ( -

+ {i18n.translate('xpack.securitySolution.timeline.sidePanel.networkDetails.title', { defaultMessage: 'Network details', })} {`: ${ip}`} -

+
); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 45b10d635195bb..4191badd6b03fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -109,8 +109,14 @@ const StatefulEventComponent: React.FC = ({ }, [event?.data]); const hostIPAddresses = useMemo(() => { - const ipList = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' }); - return ipList; + const hostIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' }) ?? []; + const sourceIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'source.ip' }) ?? []; + const destinationIpList = + getMappedNonEcsValue({ + data: event?.data, + fieldName: 'destination.ip', + }) ?? []; + return new Set([...hostIpList, ...sourceIpList, ...destinationIpList]); }, [event?.data]); const activeTab = tabType ?? TimelineTabs.query; @@ -123,7 +129,7 @@ const StatefulEventComponent: React.FC = ({ activeExpandedDetail?.params?.hostName === hostName) || (activeExpandedDetail?.panelView === 'networkDetail' && activeExpandedDetail?.params?.ip && - hostIPAddresses?.includes(activeExpandedDetail?.params?.ip)) || + hostIPAddresses?.has(activeExpandedDetail?.params?.ip)) || false; const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 32a01654bf9aff..dd701aa284997e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -151,7 +151,7 @@ const InvestigateInResolverActionComponent: React.FC !isInvestigateInResolverActionEnabled(ecsData), [ecsData]); const handleClick = useCallback(() => { dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: ecsData._id })); - if (TimelineId.active) { + if (timelineId === TimelineId.active) { dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph })); } }, [dispatch, ecsData._id, timelineId]); diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 7a9e9f07b20627..e88077679e1b62 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -34,10 +34,11 @@ import { Network } from './network'; import { Overview } from './overview'; import { Timelines } from './timelines'; import { Management } from './management'; -import { LicensingPluginStart } from '../../licensing/public'; +import { LicensingPluginStart, LicensingPluginSetup } from '../../licensing/public'; export interface SetupPlugins { home?: HomePublicPluginSetup; + licensing: LicensingPluginSetup; security: SecurityPluginSetup; triggersActionsUi: TriggersActionsSetup; usageCollection?: UsageCollectionSetup; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts index 020b70ca0553ca..95070b10b95501 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts @@ -78,7 +78,7 @@ export function registerDownloadArtifactRoute( }; if (validateDownload && !downloadArtifactResponseSchema.is(artifact)) { - return res.internalError({ body: 'Artifact failed to validate.' }); + throw new Error('Artifact failed to validate.'); } else { return res.ok(artifact); } @@ -103,7 +103,7 @@ export function registerDownloadArtifactRoute( if (err?.output?.statusCode === 404) { return res.notFound({ body: `No artifact found for ${id}` }); } else { - return res.internalError({ body: err }); + throw err; } }); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index ad5381d2ee36d6..134ce99784bfb6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -62,57 +62,52 @@ export const getMetadataListRequestHandler = function ( SecuritySolutionRequestHandlerContext > { return async (context, request, response) => { - try { - const agentService = endpointAppContext.service.getAgentService(); - if (agentService === undefined) { - throw new Error('agentService not available'); - } + const agentService = endpointAppContext.service.getAgentService(); + if (agentService === undefined) { + throw new Error('agentService not available'); + } - const metadataRequestContext: MetadataRequestContext = { - endpointAppContextService: endpointAppContext.service, - logger, - requestHandlerContext: context, - }; + const metadataRequestContext: MetadataRequestContext = { + endpointAppContextService: endpointAppContext.service, + logger, + requestHandlerContext: context, + }; - const unenrolledAgentIds = await findAllUnenrolledAgentIds( - agentService, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser - ); + const unenrolledAgentIds = await findAllUnenrolledAgentIds( + agentService, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); - const statusIDs = request?.body?.filters?.host_status?.length - ? await findAgentIDsByStatus( - agentService, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser, - request.body?.filters?.host_status - ) - : undefined; + const statusIDs = request?.body?.filters?.host_status?.length + ? await findAgentIDsByStatus( + agentService, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, + request.body?.filters?.host_status + ) + : undefined; - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); + const queryStrategy = await endpointAppContext.service + ?.getMetadataService() + ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); - const queryParams = await kibanaRequestToMetadataListESQuery( - request, - endpointAppContext, - queryStrategy!, - { - unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), - statusAgentIDs: statusIDs, - } - ); + const queryParams = await kibanaRequestToMetadataListESQuery( + request, + endpointAppContext, + queryStrategy!, + { + unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), + statusAgentIDs: statusIDs, + } + ); - const hostListQueryResult = queryStrategy!.queryResponseToHostListResult( - await context.core.elasticsearch.legacy.client.callAsCurrentUser('search', queryParams) - ); - return response.ok({ - body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), - }); - } catch (err) { - logger.warn(JSON.stringify(err, null, 2)); - return response.internalError({ body: err }); - } + const hostListQueryResult = queryStrategy!.queryResponseToHostListResult( + await context.core.elasticsearch.legacy.client.callAsCurrentUser('search', queryParams) + ); + return response.ok({ + body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), + }); }; }; @@ -129,7 +124,7 @@ export const getMetadataRequestHandler = function ( return async (context, request, response) => { const agentService = endpointAppContext.service.getAgentService(); if (agentService === undefined) { - return response.internalError({ body: 'agentService not available' }); + throw new Error('agentService not available'); } const metadataRequestContext: MetadataRequestContext = { @@ -156,7 +151,7 @@ export const getMetadataRequestHandler = function ( body: { message: err.message }, }); } - return response.internalError({ body: err }); + throw err; } }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index 132cc6dae58fff..d6b50becc2d029 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -49,11 +49,7 @@ describe('test policy response handler', () => { it('should return the latest policy response for a host', async () => { const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse()); - const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - }); + const hostPolicyResponseHandler = getHostPolicyResponseHandler(); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); const mockRequest = httpServerMock.createKibanaRequest({ @@ -72,11 +68,7 @@ describe('test policy response handler', () => { }); it('should return not found when there is no response policy for host', async () => { - const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - }); + const hostPolicyResponseHandler = getHostPolicyResponseHandler(); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(createSearchResponse()) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts index 3027892ff37452..ec1fad80701b62 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts @@ -16,25 +16,23 @@ import { EndpointAppContext } from '../../types'; import { getAgentPolicySummary, getPolicyResponseByAgentId } from './service'; import { GetAgentSummaryResponse } from '../../../../common/endpoint/types'; -export const getHostPolicyResponseHandler = function ( - endpointAppContext: EndpointAppContext -): RequestHandler, undefined> { +export const getHostPolicyResponseHandler = function (): RequestHandler< + undefined, + TypeOf, + undefined +> { return async (context, request, response) => { - try { - const doc = await getPolicyResponseByAgentId( - policyIndexPattern, - request.query.agentId, - context.core.elasticsearch.legacy.client - ); - - if (doc) { - return response.ok({ body: doc }); - } + const doc = await getPolicyResponseByAgentId( + policyIndexPattern, + request.query.agentId, + context.core.elasticsearch.legacy.client + ); - return response.notFound({ body: 'Policy Response Not Found' }); - } catch (err) { - return response.internalError({ body: err }); + if (doc) { + return response.ok({ body: doc }); } + + return response.notFound({ body: 'Policy Response Not Found' }); }; }; @@ -42,31 +40,26 @@ export const getAgentPolicySummaryHandler = function ( endpointAppContext: EndpointAppContext ): RequestHandler, undefined> { return async (context, request, response) => { - try { - const result = await getAgentPolicySummary( - endpointAppContext, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser, - request.query.package_name, - request.query?.policy_id || undefined - ); - const responseBody = { - package: request.query.package_name, - versions_count: { ...result }, - }; + const result = await getAgentPolicySummary( + endpointAppContext, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, + request.query.package_name, + request.query?.policy_id || undefined + ); + const responseBody = { + package: request.query.package_name, + versions_count: { ...result }, + }; - const body: GetAgentSummaryResponse = { - summary_response: request.query?.policy_id - ? { ...responseBody, ...{ policy_id: request.query?.policy_id } } - : responseBody, - }; + const body: GetAgentSummaryResponse = { + summary_response: request.query?.policy_id + ? { ...responseBody, ...{ policy_id: request.query?.policy_id } } + : responseBody, + }; - return response.ok({ - body, - }); - } catch (err) { - endpointAppContext.logFactory.get('metadata').error(JSON.stringify(err, null, 2)); - return response.internalError({ body: err }); - } + return response.ok({ + body, + }); }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts index 0c199890c205d9..50a68debc11255 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts @@ -26,7 +26,7 @@ export function registerPolicyRoutes(router: IRouter, endpointAppContext: Endpoi validate: GetPolicyResponseSchema, options: { authRequired: true }, }, - getHostPolicyResponseHandler(endpointAppContext) + getHostPolicyResponseHandler() ); router.get( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts index 398666b40ae380..617a1907cf4bef 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts @@ -6,7 +6,6 @@ */ import { IRouter } from 'kibana/server'; -import { EndpointAppContext } from '../types'; import { validateEvents, validateEntities, @@ -17,16 +16,14 @@ import { handleTree } from './resolver/tree/handler'; import { handleEntities } from './resolver/entity'; import { handleEvents } from './resolver/events'; -export function registerResolverRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { - const log = endpointAppContext.logFactory.get('resolver'); - +export function registerResolverRoutes(router: IRouter) { router.post( { path: '/api/endpoint/resolver/tree', validate: validateTree, options: { authRequired: true }, }, - handleTree(log) + handleTree() ); router.post( @@ -35,7 +32,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp validate: validateEvents, options: { authRequired: true }, }, - handleEvents(log) + handleEvents() ); /** diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts index edbfc4a4423e2d..6b574b3bcbc278 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts @@ -6,7 +6,7 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler, Logger } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { ResolverPaginatedEvents, SafeResolverEvent } from '../../../../common/endpoint/types'; import { validateEvents } from '../../../../common/endpoint/schema/resolver'; import { EventsQuery } from './queries/events'; @@ -28,11 +28,8 @@ function createEvents( /** * This function handles the `/events` api and returns an array of events and a cursor if more events exist than were * requested. - * @param log a logger object */ -export function handleEvents( - log: Logger -): RequestHandler< +export function handleEvents(): RequestHandler< unknown, TypeOf, TypeOf @@ -42,21 +39,16 @@ export function handleEvents( query: { limit, afterEvent }, body, } = req; - try { - const client = context.core.elasticsearch.client; - const query = new EventsQuery({ - pagination: PaginationBuilder.createBuilder(limit, afterEvent), - indexPatterns: body.indexPatterns, - timeRange: body.timeRange, - }); - const results = await query.search(client, body.filter); + const client = context.core.elasticsearch.client; + const query = new EventsQuery({ + pagination: PaginationBuilder.createBuilder(limit, afterEvent), + indexPatterns: body.indexPatterns, + timeRange: body.timeRange, + }); + const results = await query.search(client, body.filter); - return res.ok({ - body: createEvents(results, PaginationBuilder.buildCursorRequestLimit(limit, results)), - }); - } catch (err) { - log.warn(err); - return res.internalError({ body: err }); - } + return res.ok({ + body: createEvents(results, PaginationBuilder.buildCursorRequestLimit(limit, results)), + }); }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts index e7b4046b3532ad..675c861b984de2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts @@ -5,25 +5,18 @@ * 2.0. */ -import { RequestHandler, Logger } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { validateTree } from '../../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; -export function handleTree( - log: Logger -): RequestHandler> { +export function handleTree(): RequestHandler> { return async (context, req, res) => { - try { - const client = context.core.elasticsearch.client; - const fetcher = new Fetcher(client); - const body = await fetcher.tree(req.body); - return res.ok({ - body, - }); - } catch (err) { - log.warn(err); - return res.internalError({ body: 'Error retrieving tree.' }); - } + const client = context.core.elasticsearch.client; + const fetcher = new Fetcher(client); + const body = await fetcher.tree(req.body); + return res.ok({ + body, + }); }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts index 50aff5217a12ea..2179397c237044 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts @@ -112,7 +112,7 @@ describe('handlers', () => { }); describe('getTrustedAppsDeleteRouteHandler', () => { - const deleteTrustedAppHandler = getTrustedAppsDeleteRouteHandler(appContextMock); + const deleteTrustedAppHandler = getTrustedAppsDeleteRouteHandler(); it('should return ok when trusted app deleted', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -146,19 +146,18 @@ describe('handlers', () => { exceptionsListClient.deleteExceptionListItem.mockRejectedValue(error); - await deleteTrustedAppHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ params: { id: '123' } }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + deleteTrustedAppHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ params: { id: '123' } }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsCreateRouteHandler', () => { - const createTrustedAppHandler = getTrustedAppsCreateRouteHandler(appContextMock); + const createTrustedAppHandler = getTrustedAppsCreateRouteHandler(); it('should return ok with body when trusted app created', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -180,19 +179,18 @@ describe('handlers', () => { exceptionsListClient.createExceptionListItem.mockRejectedValue(error); - await createTrustedAppHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + createTrustedAppHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsListRouteHandler', () => { - const getTrustedAppsListHandler = getTrustedAppsListRouteHandler(appContextMock); + const getTrustedAppsListHandler = getTrustedAppsListRouteHandler(); it('should return ok with list when no errors', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -224,19 +222,18 @@ describe('handlers', () => { exceptionsListClient.findExceptionListItem.mockRejectedValue(error); - await getTrustedAppsListHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + getTrustedAppsListHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsSummaryHandler', () => { - const getTrustedAppsSummaryHandler = getTrustedAppsSummaryRouteHandler(appContextMock); + const getTrustedAppsSummaryHandler = getTrustedAppsSummaryRouteHandler(); it('should return ok with list when no errors', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -290,14 +287,13 @@ describe('handlers', () => { exceptionsListClient.findExceptionListItem.mockRejectedValue(error); - await getTrustedAppsSummaryHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest(), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + getTrustedAppsSummaryHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest(), + mockResponse + ) + ).rejects.toThrowError(error); }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index e7ceeee00c3069..fd5160472986f5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -16,7 +16,6 @@ import { PostTrustedAppCreateRequest, } from '../../../../common/endpoint/types'; -import { EndpointAppContext } from '../../types'; import { createTrustedApp, deleteTrustedApp, @@ -37,16 +36,12 @@ const exceptionListClientFromContext = ( return exceptionLists; }; -export const getTrustedAppsDeleteRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsDeleteRouteHandler = (): RequestHandler< DeleteTrustedAppsRequestParams, unknown, unknown, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { try { await deleteTrustedApp(exceptionListClientFromContext(context), req.params); @@ -56,75 +51,47 @@ export const getTrustedAppsDeleteRouteHandler = ( if (error instanceof MissingTrustedAppException) { return res.notFound({ body: `trusted app id [${req.params.id}] not found` }); } else { - logger.error(error); - return res.internalError({ body: error }); + throw error; } } }; }; -export const getTrustedAppsListRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsListRouteHandler = (): RequestHandler< unknown, GetTrustedAppsListRequest, unknown, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await getTrustedAppsList(exceptionListClientFromContext(context), req.query), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await getTrustedAppsList(exceptionListClientFromContext(context), req.query), + }); }; }; -export const getTrustedAppsCreateRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsCreateRouteHandler = (): RequestHandler< unknown, unknown, PostTrustedAppCreateRequest, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await createTrustedApp(exceptionListClientFromContext(context), req.body), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await createTrustedApp(exceptionListClientFromContext(context), req.body), + }); }; }; -export const getTrustedAppsSummaryRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsSummaryRouteHandler = (): RequestHandler< unknown, unknown, PostTrustedAppCreateRequest, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await getTrustedAppsSummary(exceptionListClientFromContext(context)), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await getTrustedAppsSummary(exceptionListClientFromContext(context)), + }); }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts index af23bc7025bf13..4a17b088dc8714 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts @@ -22,13 +22,9 @@ import { getTrustedAppsListRouteHandler, getTrustedAppsSummaryRouteHandler, } from './handlers'; -import { EndpointAppContext } from '../../types'; import { SecuritySolutionPluginRouter } from '../../../types'; -export const registerTrustedAppsRoutes = ( - router: SecuritySolutionPluginRouter, - endpointAppContext: EndpointAppContext -) => { +export const registerTrustedAppsRoutes = (router: SecuritySolutionPluginRouter) => { // DELETE one router.delete( { @@ -36,7 +32,7 @@ export const registerTrustedAppsRoutes = ( validate: DeleteTrustedAppsRequestSchema, options: { authRequired: true }, }, - getTrustedAppsDeleteRouteHandler(endpointAppContext) + getTrustedAppsDeleteRouteHandler() ); // GET list @@ -46,7 +42,7 @@ export const registerTrustedAppsRoutes = ( validate: GetTrustedAppsRequestSchema, options: { authRequired: true }, }, - getTrustedAppsListRouteHandler(endpointAppContext) + getTrustedAppsListRouteHandler() ); // CREATE @@ -56,7 +52,7 @@ export const registerTrustedAppsRoutes = ( validate: PostTrustedAppCreateRequestSchema, options: { authRequired: true }, }, - getTrustedAppsCreateRouteHandler(endpointAppContext) + getTrustedAppsCreateRouteHandler() ); // SUMMARY @@ -66,6 +62,6 @@ export const registerTrustedAppsRoutes = ( validate: false, options: { authRequired: true }, }, - getTrustedAppsSummaryRouteHandler(endpointAppContext) + getTrustedAppsSummaryRouteHandler() ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json index 909264c57067b4..22dba81e5c8e6a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json @@ -359,11 +359,28 @@ }, "threshold_result": { "properties": { + "terms": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "cardinality": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, "count": { "type": "long" - }, - "value": { - "type": "keyword" } } }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json index 5bd96c3442736b..e8b7fc59af6501 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json @@ -6,9 +6,11 @@ "false_positives": [ "Legitimate exchange system administration activity." ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json index 501e30a38704c5..3080a2c8c934e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json @@ -6,9 +6,11 @@ "false_positives": [ "Legitimate exchange system administration activity." ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json index f7ff34fed2eeb2..9bc8912c1a3137 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies use of WinRar or 7z to create an encrypted files. Adversaries will often compress and encrypt data in preparation for exfiltration.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json index 8f81d675a43250..aff488c9a5410c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json index b3cc7bce51d725..6f3f7ca3803f35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json @@ -19,7 +19,7 @@ "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-suricata.html", "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-zeek.html" ], - "risk_score": 100, + "risk_score": 99, "rule_id": "e7075e8d-a966-458e-a183-85cd331af255", "severity": "critical", "tags": [ @@ -55,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json index ebead5ab5e5194..b74da3bbd4d38d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Connection to Commonly Abused Web Services", - "query": "network where network.protocol == \"dns\" and\n /* Add new WebSvc domains here */\n wildcard(dns.question.name, \"*.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\"\n ) and\n /* Insert noisy false positives here */\n not process.name in (\"MicrosoftEdgeCP.exe\",\n \"MicrosoftEdge.exe\",\n \"iexplore.exe\",\n \"chrome.exe\",\n \"msedge.exe\",\n \"opera.exe\",\n \"firefox.exe\",\n \"Dropbox.exe\",\n \"slack.exe\",\n \"svchost.exe\",\n \"thunderbird.exe\",\n \"outlook.exe\",\n \"OneDrive.exe\")\n", + "query": "network where network.protocol == \"dns\" and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"*.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\"\n ) and\n /* Insert noisy false positives here */\n not process.name :\n (\n \"MicrosoftEdgeCP.exe\",\n \"MicrosoftEdge.exe\",\n \"iexplore.exe\",\n \"chrome.exe\",\n \"msedge.exe\",\n \"opera.exe\",\n \"firefox.exe\",\n \"Dropbox.exe\",\n \"slack.exe\",\n \"svchost.exe\",\n \"thunderbird.exe\",\n \"outlook.exe\",\n \"OneDrive.exe\"\n )\n", "risk_score": 21, "rule_id": "66883649-f908-4a5b-a1e0-54090a1d3a32", "severity": "low", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json index 8e9822cc610a7b..d4321263059d7f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -47,5 +48,5 @@ "value": 15 }, "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json index 40f5e928b9c5fe..809242615d9941 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json index 4aabfe552f0fd4..a7c657eae5aa86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "FTP servers should be excluded from this rule as this is expected behavior. Some business workflows may use FTP for data exchange. These workflows often have expected characteristics such as users, sources, and destinations. FTP activity involving an unusual source or destination may be more suspicious. FTP activity involving a production server that has no known associated FTP workflow or business requirement is often suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json index b0718fc2418be8..de1b3da964a8c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Command and Control via Internet Explorer", - "query": "sequence by host.id, process.entity_id with maxspan = 1s\n [process where event.type:\"start\" and process.parent.name:\"iexplore.exe\" and process.parent.args:\"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol : \"dns\" and process.name:\"iexplore.exe\" and\n not wildcard(dns.question.name, \"*.microsoft.com\", \n \"*.digicert.com\", \n \"*.msocsp.com\", \n \"*.windowsupdate.com\", \n \"*.bing.com\",\n \"*.identrust.com\")\n ]\n", - "risk_score": 43, + "query": "sequence by host.id, process.entity_id with maxspan = 1s\n [process where event.type == \"start\" and process.parent.name : \"iexplore.exe\" and process.parent.args : \"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol == \"dns\" and process.name : \"iexplore.exe\" and\n not dns.question.name :\n (\n \"*.microsoft.com\",\n \"*.digicert.com\",\n \"*.msocsp.com\",\n \"*.windowsupdate.com\",\n \"*.bing.com\",\n \"*.identrust.com\"\n )\n ]\n", + "risk_score": 47, "rule_id": "acd611f3-2b93-47b3-a0a3-7723bcc46f6d", "severity": "medium", "tags": [ @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json index 2ad30a3d376a12..80a02e38877200 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "IRC activity may be normal behavior for developers and engineers but is unusual for non-engineering end users. IRC activity involving an unusual source or destination may be more suspicious. IRC activity involving a production server is often suspicious. Because these ports are in the ephemeral range, this rule may false under certain conditions, such as when a NAT-ed web server replies to a client which has used a port in the range by coincidence. In this case, these servers can be excluded. Some legacy applications may use these ports, but this is very uncommon and usually only appears in local traffic using private IPs, which does not match this rule's conditions." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json index 7553dfefca68fb..777829a0076970 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some networks may utilize these protocols but usage that is unfamiliar to local network administrators can be unexpected and suspicious. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server with a public IP address replies to a client which has used a UDP port in the range by coincidence. This is uncommon but such servers can be excluded." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json index 6dae38320e19a9..2f33f7d3f43a8b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Servers that process email traffic may cause false positives and should be excluded from this rule as this is expected behavior." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json index fcdd23f84c8896..b90fb4a0d389c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Because this port is in the ephemeral range, this rule may false under certain conditions, such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded. Some applications may use this port but this is very uncommon and usually appears in local traffic using private IPs, which this rule does not match. Some cloud environments, particularly development environments, may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json index 572be2fad80fe7..15d042b7fe00c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some networks may utilize PPTP protocols but this is uncommon as more modern VPN technologies are available. Usage that is unfamiliar to local network administrators can be unexpected and suspicious. Torrenting applications may use this port. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server replies to a client that used this port by coincidence. This is uncommon but such servers can be excluded." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json index 51fc3c17f7e5e9..533edfb6f74412 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some proxied applications may use these ports but this usually occurs in local traffic using private IPs which this rule does not match. Proxies are widely used as a security technology but in enterprise environments this is usually local traffic which this rule does not match. If desired, internet proxy services using these ports can be added to allowlists. Some screen recording applications may use these ports. Proxy port activity involving an unusual source or destination may be more suspicious. Some cloud environments may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json index 9443a7d264562f..f8918c0420963e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some network security policies allow RDP directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. RDP services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only RDP gateways, bastions or jump servers may be expected expose RDP directly to the Internet and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json index 1e6dc210127c06..e4e1c4dee0b16f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json index 23842591116161..dd3003e346ab4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json index 5e0a6f8e3e25e1..c5f5b8cca1eede 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -62,5 +63,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json index 37f1364c5f61fe..a1cbec37c591ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Remote File Download via Script Interpreter", "query": "sequence by host.id, process.entity_id\n [network where process.name : (\"wscript.exe\", \"cscript.exe\") and network.protocol != \"dns\" and\n network.direction == \"outgoing\" and network.type == \"ipv4\" and destination.ip != \"127.0.0.1\"\n ]\n [file where event.type == \"creation\" and file.extension : (\"exe\", \"dll\")]\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "1d276579-3380-4095-ad38-e596a01bc64f", "severity": "medium", "tags": [ @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json index 95b07dab428271..5d6efd0802351b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "NATed servers that process email traffic may false and should be excluded from this rule as this is expected behavior for them. Consumer and personal devices may send email traffic to remote Internet destinations. In this case, such devices or networks can be excluded from this rule if this is expected behavior." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json index 4bd7559e3878f6..e7a5e4c386fce0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired. Some cloud environments may use this port when VPNs or direct connects are not in use and database instances are accessed directly across the Internet." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json index f081fba9c1babe..ff97c392ccbe64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some network security policies allow SSH directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. SSH services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only SSH gateways, bastions or jump servers may be expected expose SSH directly to the Internet and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json index b92bf9065693aa..fdf4dfe0e8a7e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "SSH connections may be made directly to Internet destinations in order to access Linux cloud server instances but such connections are usually made only by engineers. In such cases, only SSH gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json index e83fbcc58f4cda..0bf7ac92fdaf69 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json index 747e3b87d4e49c..5c570a2471eaea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "IoT (Internet of Things) devices and networks may use telnet and can be excluded if desired. Some business work-flows may use Telnet for administration of older devices. These often have a predictable behavior. Telnet activity involving an unusual source or destination may be more suspicious. Telnet activity involving a production server that has no known associated Telnet work-flow or business requirement is often suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json index 38666db032c579..7d302ba4062a51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Tor client activity is uncommon in managed enterprise networks but may be common in unmanaged or public networks where few security policies apply. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used one of these ports by coincidence. In this case, such servers can be excluded if desired." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json index 53572c125f4e77..81789b415378cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "VNC connections may be received directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work-flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -59,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json index 06b55dc499d052..9ceae3f436a7f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "VNC connections may be made directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json new file mode 100644 index 00000000000000..4473933bf8521b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a process with arguments pointing to known browser files that store passwords and cookies. Adversaries may acquire credentials from web browsers by reading files specific to the target browser.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Access of Stored Browser Credentials", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args :\n (\n \"/Users/*/Library/Application Support/Google/Chrome/Default/Login Data\", \n \"/Users/*/Library/Application Support/Google/Chrome/Default/Cookies\", \n \"/Users/*/Library/Cookies*\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/cookies.sqlite\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/key*.db\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/logins.json\", \n \"Login Data\",\n \"Cookies.binarycookies\", \n \"key4.db\", \n \"key3.db\", \n \"logins.json\", \n \"cookies.sqlite\"\n )\n", + "references": [ + "https://securelist.com/calisto-trojan-for-macos/86543/" + ], + "risk_score": 73, + "rule_id": "20457e4f-d1de-4b92-ae69-142e27a4342a", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.003", + "name": "Credentials from Web Browsers", + "reference": "https://attack.mitre.org/techniques/T1555/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json index dbb9c1af14d846..a411ccecc1fc9b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json new file mode 100644 index 00000000000000..d7dbb660b7d612 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of a compression utility to collect known files containing sensitive information, such as credentials and system configurations.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Sensitive Files Compression", + "query": "event.category:process and event.type:start and process.name:(zip or tar or gzip or hdiutil or 7z) and process.args: ( /root/.ssh/id_rsa or /root/.ssh/id_rsa.pub or /root/.ssh/id_ed25519 or /root/.ssh/id_ed25519.pub or /root/.ssh/authorized_keys or /root/.ssh/authorized_keys2 or /root/.ssh/known_hosts or /root/.bash_history or /etc/hosts or /home/*/.ssh/id_rsa or /home/*/.ssh/id_rsa.pub or /home/*/.ssh/id_ed25519 or /home/*/.ssh/id_ed25519.pub or /home/*/.ssh/authorized_keys or /home/*/.ssh/authorized_keys2 or /home/*/.ssh/known_hosts or /home/*/.bash_history or /root/.aws/credentials or /root/.aws/config or /home/*/.aws/credentials or /home/*/.aws/config or /root/.docker/config.json or /home/*/.docker/config.json or /etc/group or /etc/passwd or /etc/shadow or /etc/gshadow )", + "references": [ + "https://www.trendmicro.com/en_ca/research/20/l/teamtnt-now-deploying-ddos-capable-irc-bot-tntbotinger.html" + ], + "risk_score": 47, + "rule_id": "6b84d470-9036-4cc0-a27c-6d90bbfe81ab", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Collection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1552", + "name": "Unsecured Credentials", + "reference": "https://attack.mitre.org/techniques/T1552/", + "subtechnique": [ + { + "id": "T1552.001", + "name": "Credentials In Files", + "reference": "https://attack.mitre.org/techniques/T1552/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0009", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "technique": [ + { + "id": "T1560", + "name": "Archive Collected Data", + "reference": "https://attack.mitre.org/techniques/T1560/", + "subtechnique": [ + { + "id": "T1560.001", + "name": "Archive via Utility", + "reference": "https://attack.mitre.org/techniques/T1560/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json new file mode 100644 index 00000000000000..eb798dcc2cb285 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json @@ -0,0 +1,59 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a Chromium based browser with the debugging process argument, which may indicate an attempt to steal authentication cookies. An adversary may steal web application or service session cookies and use them to gain access web applications or Internet services as an authenticated user without needing credentials.", + "false_positives": [ + "Developers performing browsers plugin or extension debugging." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "max_signals": 33, + "name": "Potential Cookies Theft via Browser Debugging", + "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name in (\n \"Microsoft Edge\",\n \"chrome.exe\",\n \"Google Chrome\",\n \"google-chrome-stable\",\n \"google-chrome-beta\",\n \"google-chrome\",\n \"msedge.exe\") and\n process.args : (\"--remote-debugging-port=*\", \n \"--remote-debugging-targets=*\", \n \"--remote-debugging-pipe=*\") and\n process.args : \"--user-data-dir=*\" and not process.args:\"--remote-debugging-port=0\"\n", + "references": [ + "https://github.com/defaultnamehere/cookie_crimes", + "https://embracethered.com/blog/posts/2020/cookie-crimes-on-mirosoft-edge/", + "https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/post/multi/gather/chrome_cookies.md", + "https://posts.specterops.io/hands-in-the-cookie-jar-dumping-cookies-with-chromiums-remote-debugger-port-34c4f468844e" + ], + "risk_score": 47, + "rule_id": "027ff9ea-85e7-42e3-99d2-bbb7069e02eb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Windows", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1539", + "name": "Steal Web Session Cookie", + "reference": "https://attack.mitre.org/techniques/T1539/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json index 1750bd180b6af3..8df6a9c7199571 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json index b3f83e24655eb3..289b959ece15ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json index 51e008c848b495..a70ff26c3c0c70 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json @@ -2,18 +2,19 @@ "author": [ "Elastic" ], - "description": "Adversaries may collect the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features such as WiFi passwords, websites, secure notes, certificates, and Kerberos.", + "description": "Adversaries may collect the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features such as WiFi passwords, websites, secure notes and certificates.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "kuery", + "language": "eql", "license": "Elastic License", - "name": "Compression of Keychain Credentials Directories", - "query": "event.category:process and event.type:(start or process_started) and process.name:(zip or tar or gzip or 7za or hdiutil) and process.args:(\"/Library/Keychains/\" or \"/Network/Library/Keychains/\" or \"~/Library/Keychains/\")", + "name": "Access to Keychain Credentials Directories", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args :\n (\n \"/Users/*/Library/Keychains/*\",\n \"/Library/Keychains/*\",\n \"/Network/Library/Keychains/*\",\n \"System.keychain\",\n \"login.keychain-db\",\n \"login.keychain\"\n )\n", "references": [ - "https://objective-see.com/blog/blog_0x25.html" + "https://objective-see.com/blog/blog_0x25.html", + "https://securelist.com/calisto-trojan-for-macos/86543/" ], "risk_score": 73, "rule_id": "96e90768-c3b7-4df6-b5d9-6237f8bc36a8", @@ -50,6 +51,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 3 + "type": "eql", + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json index f3c6b1c785fe57..6188dbe9fc0c02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json index 474f4f0f7c617c..5cb84bb2a2ef22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Credential Acquisition via Registry Hive Dumping", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\") and\n not process.parent.executable : \"C:\\\\Program Files*\\\\Rapid7\\\\Insight Agent\\\\components\\\\insight_agent\\\\*\\\\ir_agent.exe\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\")\n", "references": [ "https://medium.com/threatpunter/detecting-attempts-to-steal-passwords-from-the-registry-7512674487f8" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json new file mode 100644 index 00000000000000..f27e8c4272c09a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands used to dump user account hashes. Adversaries may attempt to dump credentials to obtain account login information in the form of a hash. These hashes can be cracked or leveraged for lateral movement.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Dumping Account Hashes via Built-In Commands", + "query": "event.category:process and event.type:start and process.name:(defaults or mkpassdb) and process.args:(ShadowHashData or \"-dump\")", + "references": [ + "https://apple.stackexchange.com/questions/186893/os-x-10-9-where-are-password-hashes-stored", + "https://www.unix.com/man-page/osx/8/mkpassdb/" + ], + "risk_score": 73, + "rule_id": "02ea4563-ec10-4974-b7de-12e65aa4f9b3", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json new file mode 100644 index 00000000000000..f8524c589ea0f4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may dump the content of the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Dumping of Keychain Content via Security Command", + "query": "process where event.type in (\"start\", \"process_started\") and process.args : \"dump-keychain\" and process.args : \"-d\"\n", + "references": [ + "https://ss64.com/osx/security.html" + ], + "risk_score": 73, + "rule_id": "565d6ca5-75ba-4c82-9b13-add25353471c", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json index 428272d6447cbd..830721ef464542 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json index f810ba740738db..2dab1711f9009c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index d6a6aa14cba260..d4251313f75dea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json @@ -9,13 +9,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Kerberos Traffic from Unusual Process", "query": "network where event.type == \"start\" and network.direction == \"outgoing\" and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert False Positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "897dc6b5-b39f-432a-8d75-d3730d50c782", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json new file mode 100644 index 00000000000000..74cfa19caf1ea3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may collect keychain storage data from a system to in order to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos.", + "false_positives": [ + "Trusted parent processes accessing their respective application passwords." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Keychain Password Retrieval via Command Line", + "query": "event.category:process and event.type:(start or process_started) and process.name:security and process.args:(\"find-generic-password\" or \"find-internet-password\")", + "references": [ + "https://www.netmeister.org/blog/keychain-passwords.html", + "https://github.com/priyankchheda/chrome_password_grabber/blob/master/chrome.py", + "https://ss64.com/osx/security.html", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/" + ], + "risk_score": 73, + "rule_id": "9092cd6c-650f-4fa3-8a8a-28256c7489c9", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json index ef1d14add5b218..c1e0f9f7e7a638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json index 3a037985b61485..8f1090f99414e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json new file mode 100644 index 00000000000000..311723651cb3e1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the built-in networksetup command to configure webproxy settings. This may indicate an attempt to hijack web browser traffic for credential access via traffic sniffing or redirection.", + "false_positives": [ + "Legitimate WebProxy Settings Modification" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "WebProxy Settings Modification", + "query": "event.category:process and event.type:start and process.name:networksetup and process.args:(\"-setwebproxy\" or \"-setsecurewebproxy\" or \"-setautoproxyurl\")", + "references": [ + "https://unit42.paloaltonetworks.com/mac-malware-steals-cryptocurrency-exchanges-cookies/", + "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" + ], + "risk_score": 47, + "rule_id": "10a500bb-a28f-418e-ba29-ca4c8d1a9f2f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1539", + "name": "Steal Web Session Cookie", + "reference": "https://attack.mitre.org/techniques/T1539/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json new file mode 100644 index 00000000000000..84f1315472d7a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to modify the WDigest security provider in the registry to force the user's password to be stored in clear text in memory. This behavior can be indicative of an adversary attempting to weaken the security configuration of an endpoint. Once the UseLogonCredential value is modified, the adversary may attempt to dump clear text passwords from memory.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Modification of WDigest Security Provider", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path:\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\" and\n registry.data.strings:\"1\"\n", + "references": [ + "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", + "https://www.praetorian.com/blog/mitigating-mimikatz-wdigest-cleartext-credential-theft?edition=2019" + ], + "risk_score": 73, + "rule_id": "d703a5af-d5b0-43bd-8ddb-7a5d500b7da5", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.001", + "name": "LSASS Memory", + "reference": "https://attack.mitre.org/techniques/T1003/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json index f8bb27bf1d822c..e369c6e87d3e02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License", "name": "Prompt for Credentials with OSASCRIPT", - "query": "process where event.type in (\"start\", \"process_started\") and process.name:\"osascript\" and process.args:\"-e\" and process.args:\"password\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*display dialog*password*\"\n", "references": [ "https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/prompt.py", "https://ss64.com/osx/osascript.html" @@ -38,12 +38,19 @@ { "id": "T1056", "name": "Input Capture", - "reference": "https://attack.mitre.org/techniques/T1056/" + "reference": "https://attack.mitre.org/techniques/T1056/", + "subtechnique": [ + { + "id": "T1056.002", + "name": "GUI Input Capture", + "reference": "https://attack.mitre.org/techniques/T1056/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json new file mode 100644 index 00000000000000..f9efa7f47b9961 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Searching for Saved Credentials via VaultCmd", + "query": "process where event.type in (\"start\", \"process_started\") and\n (process.pe.original_file_name:\"vaultcmd.exe\" or process.name:\"vaultcmd.exe\") and\n process.args:\"/list*\"\n", + "references": [ + "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", + "https://rastamouse.me/blog/rdp-jump-boxes/" + ], + "risk_score": 47, + "rule_id": "be8afaed-4bcd-4e0a-b5f9-5562003dde81", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json new file mode 100644 index 00000000000000..873763ce3048c6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json @@ -0,0 +1,68 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a Secure Shell (SSH) client or server process creating or writing to a known SSH backdoor log file. Adversaries may modify SSH related binaries for persistence or credential access via patching sensitive functions to enable unauthorized access or to log SSH credentials for exfiltration.", + "false_positives": [ + "Updates to approved and trusted SSH executables can trigger this rule." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential OpenSSH Backdoor Logging Activity", + "query": "file where event.type == \"change\" and process.executable : (\"/usr/sbin/sshd\", \"/usr/bin/ssh\") and\n (\n file.name : (\".*\", \"~*\") or\n file.extension : (\"in\", \"out\", \"ini\", \"h\", \"gz\", \"so\", \"sock\", \"sync\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\") or\n file.path : \n (\n \"/private/etc/*--\", \n \"/usr/share/*\", \n \"/usr/include/*\", \n \"/usr/local/include/*\", \n \"/private/tmp/*\", \n \"/private/var/tmp/*\",\n \"/usr/tmp/*\", \n \"/usr/share/man/*\", \n \"/usr/local/share/*\", \n \"/usr/lib/*.so.*\", \n \"/private/etc/ssh/.sshd_auth\",\n \"/usr/bin/ssd\", \n \"/private/var/opt/power\", \n \"/private/etc/ssh/ssh_known_hosts\", \n \"/private/var/html/lol\", \n \"/private/var/log/utmp\", \n \"/private/var/lib\",\n \"/var/run/sshd/sshd.pid\",\n \"/var/run/nscd/ns.pid\",\n \"/var/run/udev/ud.pid\",\n \"/var/run/udevd.pid\"\n )\n )\n", + "references": [ + "https://github.com/eset/malware-ioc/tree/master/sshdoor", + "https://www.welivesecurity.com/wp-content/uploads/2021/01/ESET_Kobalos.pdf" + ], + "risk_score": 73, + "rule_id": "f28e2be4-6eca-4349-bdd9-381573730c22", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Persistence", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1554", + "name": "Compromise Client Software Binary", + "reference": "https://attack.mitre.org/techniques/T1554/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json new file mode 100644 index 00000000000000..e6ca43ead126ce --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos. Adversaries may collect the keychain storage data from a system to acquire credentials.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SystemKey Access via Command Line", + "query": "event.category:process and event.type:(start or process_started) and process.args:\"/private/var/db/SystemKey\"", + "references": [ + "https://github.com/AlessandroZ/LaZagne/blob/master/Mac/lazagne/softwares/system/chainbreaker.py" + ], + "risk_score": 73, + "rule_id": "d75991f2-b989-419d-b797-ac1e54ec2d61", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json index 878f2532cf1c51..ea97da66d52cf0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json new file mode 100644 index 00000000000000..5c577a494b1794 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies changes to the SoftwareUpdate preferences using the built-in defaults command. Adversaries may abuse this in an attempt to disable security updates.", + "false_positives": [ + "Authorized SoftwareUpdate Settings Changes" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SoftwareUpdate Preferences Modification", + "query": "event.category:process and event.type:(start or process_started) and process.name:defaults and process.args:(write and \"-bool\" and (com.apple.SoftwareUpdate or /Library/Preferences/com.apple.SoftwareUpdate.plist) and not (TRUE or true))", + "references": [ + "https://blog.checkpoint.com/2017/07/13/osxdok-refuses-go-away-money/" + ], + "risk_score": 47, + "rule_id": "f683dcdf-a018-4801-b066-193d4ae6c8e5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json index bfa8c19d8ea779..4ecb25a2d6de1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json @@ -11,11 +11,12 @@ "language": "eql", "license": "Elastic License", "name": "Attempt to Remove File Quarantine Attribute", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.name == \"xattr\" and process.args == \"com.apple.quarantine\" and process.args == \"-d\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args : \"xattr\" and\n (\n (process.args : \"com.apple.quarantine\" and process.args : (\"-d\", \"-w\")) or\n (process.args : \"-c\" and process.command_line :\n (\n \"/bin/bash -c xattr -c *\",\n \"/bin/zsh -c xattr -c *\",\n \"/bin/sh -c xattr -c *\"\n )\n )\n )\n", "references": [ - "https://www.trendmicro.com/en_us/research/20/k/new-macos-backdoor-connected-to-oceanlotus-surfaces.html" + "https://www.trendmicro.com/en_us/research/20/k/new-macos-backdoor-connected-to-oceanlotus-surfaces.html", + "https://ss64.com/osx/xattr.html" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "f0b48bbc-549e-4bcf-8ee0-a7a72586c6a7", "severity": "medium", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json new file mode 100644 index 00000000000000..e351a48e2b1eb6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects attempts to disable Gatekeeper on macOS. Gatekeeper is a security feature that's designed to ensure that only trusted software is run. Adversaries may attempt to disable Gatekeeper before executing malicious code.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Disable Gatekeeper", + "query": "event.category:process and event.type:(start or process_started) and process.args:(spctl and \"--master-disable\")", + "references": [ + "https://support.apple.com/en-us/HT202491", + "https://www.carbonblack.com/blog/tau-threat-intelligence-notification-new-macos-malware-variant-of-shlayer-osx-discovered/" + ], + "risk_score": 47, + "rule_id": "4da13d6e-904f-4636-81d8-6ab14b4e6ae9", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json index d99f2f90e130a1..cc5b948fbf20f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json new file mode 100644 index 00000000000000..0a19ea075926bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic", + "Anabella Cristaldi" + ], + "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Windows Event Logs Cleared", + "query": "event.action:(\"audit-log-cleared\" or \"Log clear\")", + "risk_score": 21, + "rule_id": "45ac4800-840f-414c-b221-53dd36a5aaf7", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1070", + "name": "Indicator Removal on Host", + "reference": "https://attack.mitre.org/techniques/T1070/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json index 00c7a5fce40528..c5a9c7e315920b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", "name": "Suspicious Process from Conhost", - "query": "event.category:process and event.type:(start or process_started) and process.parent.name:conhost.exe", + "query": "event.category:process and event.type:(start or process_started) and process.parent.name:conhost.exe and not process.executable:(\"C:\\Windows\\splwow64.exe\" or \"C:\\Windows\\System32\\WerFault.exe\" or \"C:\\\\Windows\\System32\\conhost.exe\")", "references": [ "https://modexp.wordpress.com/2018/09/12/process-injection-user-data/", "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Defense%20Evasion/evasion_codeinj_odzhan_conhost_sysmon_10_1.evtx" @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json new file mode 100644 index 00000000000000..9ec4e7a9a0ab8a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a local trusted root certificate in Windows. The install of a malicious root certificate would allow an attacker the ability to masquerade malicious files as valid signed components from any entity (e.g. Microsoft). It could also allow an attacker to decrypt SSL traffic.", + "false_positives": [ + "Certain applications may install root certificates for the purpose of inspecting SSL traffic." + ], + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation or Modification of Root Certificate", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path :\n (\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\"\n )\n", + "references": [ + "https://posts.specterops.io/code-signing-certificate-cloning-attacks-and-defenses-6f98657fc6ec", + "https://www.ired.team/offensive-security/persistence/t1130-install-root-certificate" + ], + "risk_score": 21, + "rule_id": "203ab79b-239b-4aa5-8e54-fc50623ee8e4", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.004", + "name": "Install Root Certificate", + "reference": "https://attack.mitre.org/techniques/T1553/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json index f97901404fdc76..ddd3e20ac1c7cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json @@ -4,7 +4,8 @@ ], "description": "A spoofing vulnerability exists in the way Windows CryptoAPI (Crypt32.dll) validates Elliptic Curve Cryptography (ECC) certificates. An attacker could exploit the vulnerability by using a spoofed code-signing certificate to sign a malicious executable, making it appear the file was from a trusted, legitimate source.", "index": [ - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json new file mode 100644 index 00000000000000..30f4b9883624b0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json @@ -0,0 +1,62 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the Windows Defender registry settings to disable the service or set the service to be started manually.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Windows Defender Disabled via Registry Modification", + "note": "Detections should be investigated to identify if the hosts and users are authorized to use this tool. As this rule detects post-exploitation process activity, investigations into this should be prioritized", + "query": "registry where event.type in (\"creation\", \"change\") and\n ((registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings:\"1\") or\n (registry.path:\"HKLM\\\\System\\\\ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\")))\n", + "references": [ + "https://thedfirreport.com/2020/12/13/defender-control/" + ], + "risk_score": 21, + "rule_id": "2ffa1f1e-b6db-47fa-994b-1512743847eb", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.006", + "name": "Indicator Blocking", + "reference": "https://attack.mitre.org/techniques/T1562/006/" + }, + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json index cee43c94d97fd2..5630e20e0e65f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json index 9d61ae658f182e..39c2dc65e6ef7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json index 86820f7203bfa6..d00a4df394e5c6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json @@ -7,7 +7,8 @@ "index": [ "auditbeat-*", "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json index d4b84879bcf7d6..efd7d43c90c858 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json @@ -2,16 +2,16 @@ "author": [ "Elastic" ], - "description": "Adversaries may attempt to clear the bash command line history in an attempt to evade detection or forensic investigations.", + "description": "Adversaries may attempt to clear or disable the Bash command-line history in an attempt to evade detection or forensic investigations.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "lucene", + "language": "eql", "license": "Elastic License", - "name": "Deletion of Bash Command Line History", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:rm AND process.args:/\\/(home\\/.{1,255}|root)\\/\\.bash_history/", + "name": "Tampering of Bash Command-Line History", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n (process.args : (\"rm\", \"echo\") and process.args : (\".bash_history\", \"/root/.bash_history\", \"/home/*/.bash_history\")) or\n (process.name : \"history\" and process.args : \"-c\") or\n (process.args : \"export\" and process.args : (\"HISTFILE=/dev/null\", \"HISTFILESIZE=0\")) or\n (process.args : \"unset\" and process.args : \"HISTFILE\") or\n (process.args : \"set\" and process.args : \"history\" and process.args : \"+o\")\n )\n", "risk_score": 47, "rule_id": "7bcbb3ac-e533-41ad-a612-d6c3bf666aba", "severity": "medium", @@ -47,6 +47,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 5 + "type": "eql", + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json index 965b1592f32887..62e785b091915b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json index ed2ee27a2f9e70..49c69d0a83862f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json index 14b92c0eb8f602..fdbb0f0e35a09d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json index cdae07b5e93bb9..fefa5963866a86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json index 21568e4531ec86..b8b0201cb5db7b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json index b0c98c13f5331a..14436f937b4187 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -56,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json index 9df293e6cbf3f6..d6976f0c287da9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json index 61f17c8d593df1..f02a8543689b14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json index 763c17e2792aed..ab2b1ee09c0c14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json index 6bd60eb4520930..98af8429f28271 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -54,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json index c1679e4ce1c63f..07c13af09d8d38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json index 10424863f0290b..d7ea84de18ec87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json @@ -6,9 +6,11 @@ "false_positives": [ "These programs may be used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -52,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json new file mode 100644 index 00000000000000..55ebb2e428e1e9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Masquerading can allow an adversary to evade defenses and better blend in with the environment. One way it occurs is when the name or location of a file is manipulated as a means of tricking a user into executing what they think is a benign file type but is actually executable code.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Executable File Creation with Multiple Extensions", + "query": "file where event.type == \"creation\" and file.extension:\"exe\" and\n file.name:\n (\n \"*.vbs.exe\",\n \"*.vbe.exe\",\n \"*.bat.exe\",\n \"*.js.exe\",\n \"*.cmd.exe\",\n \"*.wsh.exe\",\n \"*.ps1.exe\",\n \"*.pdf.exe\",\n \"*.docx.exe\",\n \"*.doc.exe\",\n \"*.xlsx.exe\",\n \"*.xls.exe\",\n \"*.pptx.exe\",\n \"*.ppt.exe\",\n \"*.txt.exe\",\n \"*.rtf.exe\",\n \"*.gif.exe\",\n \"*.jpg.exe\",\n \"*.png.exe\",\n \"*.bmp.exe\",\n \"*.hta.exe\",\n \"*.txt.exe\",\n \"*.img.exe\",\n \"*.iso.exe\"\n )\n", + "risk_score": 47, + "rule_id": "8b2b3a62-a598-4293-bc14-3d5fa22bb98f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1036", + "name": "Masquerading", + "reference": "https://attack.mitre.org/techniques/T1036/", + "subtechnique": [ + { + "id": "T1036.004", + "name": "Masquerade Task or Service", + "reference": "https://attack.mitre.org/techniques/T1036/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json index e294c84db2d080..3b03c150476135 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json index c2e82ca10a99c4..7e64762ea7cad4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -42,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json index 6c416b0a4e0c5f..c85e457823b066 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json @@ -7,7 +7,8 @@ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], "index": [ - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json new file mode 100644 index 00000000000000..1548566df04943 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may install a root certificate on a compromised system to avoid warnings when connecting to their command and control servers. Root certificates are used in public key cryptography to identify a root certificate authority (CA). When a root certificate is installed, the system or application will trust certificates in the root's chain of trust that have been signed by the root certificate.", + "false_positives": [ + "Certain applications may install root certificates for the purpose of inspecting SSL traffic." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Install Root Certificate", + "query": "event.category:process and event.type:(start or process_started) and process.name:security and process.args:\"add-trusted-cert\"", + "references": [ + "https://ss64.com/osx/security-cert.html" + ], + "risk_score": 47, + "rule_id": "bc1eeacf-2972-434f-b782-3a532b100d67", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.004", + "name": "Install Root Certificate", + "reference": "https://attack.mitre.org/techniques/T1553/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json index 0dd2e51995c9be..b71e627fba8130 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json index 95042ffeebd0fc..eb9798a8c5c5f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json index f9b6c3082ef9cc..eb267f2a91b46c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json index 559d81f963abb0..5d344cfaeab606 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -17,7 +18,8 @@ "query": "event.category:process and event.type:(start or process_started) and process.parent.name:WerFault.exe and not process.name:(cofire.exe or psr.exe or VsJITDebugger.exe or TTTracer.exe or rundll32.exe)", "references": [ "https://www.hexacorn.com/blog/2019/09/19/silentprocessexit-quick-look-under-the-hood/", - "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Persistence/persistence_SilentProcessExit_ImageHijack_sysmon_13_1.evtx" + "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Persistence/persistence_SilentProcessExit_ImageHijack_sysmon_13_1.evtx", + "https://blog.menasec.net/2021/01/" ], "risk_score": 47, "rule_id": "ac5012b8-8da8-440b-aaaf-aedafdea2dff", @@ -48,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json index 4bea1f2f2e6681..c575d2755251be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Program Files Directory Masquerading", "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n /* capture both fake program files directory in process executable as well as if passed in process args as a dll*/\n process.args : (\"C:\\\\*Program*Files*\\\\*\", \"C:\\\\*Program*Files*\\\\*\") and\n not process.args : (\"C:\\\\Program Files\\\\*\", \"C:\\\\Program Files (x86)\\\\*\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "32c5cf9c-2ef8-4e87-819e-5ccb7cd18b14", "severity": "medium", "tags": [ @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json index 39cf9860dadcdf..0fb020c2bf3742 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json index 686e9a1d4fa492..47b7c3d26f040a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -49,5 +50,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json index 78a22ce98675aa..28b11dc5168b5b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json new file mode 100644 index 00000000000000..a1ed10fc7dbe91 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to an environment variable using the built-in launchctl command. Adversaries may execute their own malicious payloads by hijacking certain environment variables to load arbitrary libraries or bypass certain restrictions.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Environment Variable via Launchctl", + "query": "event.category:process and event.type:start and process.name:launchctl and process.args:(setenv and not (JAVA*_HOME or RUNTIME_JAVA_HOME or DBUS_LAUNCHD_SESSION_BUS_SOCKET or ANT_HOME))", + "references": [ + "https://github.com/rapid7/metasploit-framework/blob/master//modules/post/osx/escalate/tccbypass.rb" + ], + "risk_score": 47, + "rule_id": "7453e19e-3dbf-4e4e-9ae0-33d6c6ed15e1", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.007", + "name": "Path Interception by PATH Environment Variable", + "reference": "https://attack.mitre.org/techniques/T1574/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json index b157c7f16f9ac2..361796104a9a7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json index f3da8be382f469..9947b261c6ef3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json index b20766548cb3e6..bed35a87235922 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json index 88fd8a2054ee84..75cf701de1e461 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json index 29e24822e36964..547630e0fa84b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json index 5f8975c41cf183..6ff8025ac0cd52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json new file mode 100644 index 00000000000000..5f003354393197 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of sqlite3 to directly modify the Transparency, Consent, and Control (TCC) SQLite database. This may indicate an attempt to bypass macOS privacy controls, including access to sensitive resources like the system camera, microphone, address book, and calendar.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Privacy Control Bypass via TCCDB Modification", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"sqlite*\" and \n process.args : \"/*/Application Support/com.apple.TCC/TCC.db\"\n", + "references": [ + "https://applehelpwriter.com/2016/08/29/discovering-how-dropbox-hacks-your-mac/", + "https://github.com/bp88/JSS-Scripts/blob/master/TCC.db Modifier.sh", + "https://medium.com/@mattshockl/cve-2020-9934-bypassing-the-os-x-transparency-consent-and-control-tcc-framework-for-4e14806f1de8" + ], + "risk_score": 47, + "rule_id": "eea82229-b002-470e-a9e1-00be38b14d32", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json new file mode 100644 index 00000000000000..fad033bd417573 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies use of the Secure Copy Protocol (SCP) to copy files locally by abusing the auto addition of the Secure Shell Daemon (sshd) to the authorized application list for Full Disk Access. This may indicate attempts to bypass macOS privacy controls to access sensitive files.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Privacy Control Bypass via Localhost Secure Copy", + "query": "process where event.type in (\"start\", \"process_started\") and \n process.name:\"scp\" and\n process.args:\"StrictHostKeyChecking=no\" and \n process.command_line:(\"scp *localhost:/*\", \"scp *127.0.0.1:/*\") and\n not process.args:\"vagrant@*127.0.0.1*\"\n", + "references": [ + "https://blog.trendmicro.com/trendlabs-security-intelligence/xcsset-mac-malware-infects-xcode-projects-performs-uxss-attack-on-safari-other-browsers-leverages-zero-day-exploits/" + ], + "risk_score": 73, + "rule_id": "c02c8b9f-5e1d-463c-a1b0-04edcdfe1a3d", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json index 8dbc0e5ef76fef..54938bc2d1d6f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json new file mode 100644 index 00000000000000..5a08f3bd90855b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies changes to the Safari configuration using the built-in defaults command. Adversaries may attempt to enable or disable certain Safari settings, such as enabling JavaScript from Apple Events to ease in the hijacking of the users browser.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Safari Settings via Defaults Command", + "query": "event.category:process and event.type:start and process.name:defaults and process.args: (com.apple.Safari and write and not ( UniversalSearchEnabled or SuppressSearchSuggestions or WebKitTabToLinksPreferenceKey or ShowFullURLInSmartSearchField or com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks ) )", + "references": [ + "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" + ], + "risk_score": 47, + "rule_id": "6482255d-f468-45ea-a5b3-d3a7de1331ae", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json new file mode 100644 index 00000000000000..9542eed5a6060b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json @@ -0,0 +1,33 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a suspicious zip file prepended with special characters. Sandboxed Microsoft Office applications on macOS are allowed to write files that start with special characters, which can be combined with an AutoStart location to achieve sandbox evasion.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Microsoft Office Sandbox Evasion", + "query": "event.category:file and not event.type:deletion and file.name:~$*.zip", + "references": [ + "https://i.blackhat.com/USA-20/Wednesday/us-20-Wardle-Office-Drama-On-macOS.pdf", + "https://www.mdsec.co.uk/2018/08/escaping-the-sandbox-microsoft-office-on-macos/", + "https://desi-jarvis.medium.com/office365-macos-sandbox-escape-fcce4fa4123c" + ], + "risk_score": 73, + "rule_id": "d22a85c6-d2ad-4cc4-bf7b-54787473669a", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json index 0176016d89f4de..e6cb50ab79f4b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json index eb01d608e54b75..a67e09916b783c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Secure File Deletion via SDelete Utility", "note": "Verify process details such as command line and hash to confirm this activity legitimacy.", - "query": "file where event.type == \"change\" and wildcard(file.name,\"*AAA.AAA\")\n", + "query": "file where event.type == \"change\" and file.name : \"*AAA.AAA\"\n", "risk_score": 21, "rule_id": "5aee924b-6ceb-4633-980e-1bde8cdb40c5", "severity": "low", @@ -49,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json new file mode 100644 index 00000000000000..4eea43aada3a8c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "SIP Provider Modification", + "query": "registry where event.type:\"change\" and\n registry.path: (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\"\n ) and\n registry.data.strings:\"*.dll\"\n", + "references": [ + "https://github.com/mattifestation/PoCSubjectInterfacePackage" + ], + "risk_score": 47, + "rule_id": "f2c7b914-eda3-40c2-96ac-d23ef91776ca", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.003", + "name": "SIP and Trust Provider Hijacking", + "reference": "https://attack.mitre.org/techniques/T1553/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json index 0b5ca834c9c8f9..f9a94e85f5a470 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json index 4ee3172fea7ebc..d5df975cfbd871 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -51,5 +52,5 @@ "value": 10 }, "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json index f6722e13eaf056..fbd006936253ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json index 2f0f762031faa5..4f464327706609 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Windows Suspicious Script Object Execution", - "query": "/* add winlogbeat-* when process.code_signature.* fields are populated */\n\nsequence by process.entity_id with maxspan=2m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n /* process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true and */\n not (process.name : \"cscript.exe\" or\n process.name : \"iexplore.exe\" or\n process.name : \"MicrosoftEdge.exe\" or\n process.name : \"msiexec.exe\" or\n process.name : \"smartscreen.exe\" or\n process.name : \"taskhostw.exe\" or\n process.name : \"w3wp.exe\" or\n process.name : \"wscript.exe\")]\n [library where event.type == \"start\" and file.name : \"scrobj.dll\"]\n", + "query": "/* add winlogbeat-* when process.code_signature.* fields are populated */\n\nsequence by process.entity_id with maxspan = 2m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n /* process.code_signature.subject_name : \"Microsoft Corporation\" and process.code_signature.trusted : true and */\n not process.name : (\n \"cscript.exe\",\n \"iexplore.exe\",\n \"MicrosoftEdge.exe\",\n \"msiexec.exe\",\n \"smartscreen.exe\",\n \"taskhostw.exe\",\n \"w3wp.exe\",\n \"wscript.exe\")]\n [library where event.type == \"start\" and dll.name : \"scrobj.dll\"]\n", "risk_score": 21, "rule_id": "4ed678a9-3a4f-41fb-9fea-f85a6e0a0dff", "severity": "medium", @@ -34,5 +35,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json index e9224162643595..7c6998f929e090 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json @@ -5,12 +5,14 @@ "description": "Identifies WMIC whitelisting bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of a whitelist bypass.", "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious WMIC XSL Script Execution", - "query": "sequence by process.entity_id with maxspan=2m\n[process where event.type in (\"start\", \"process_started\") and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n wildcard(process.args, \"format*:*\", \"/format*:*\", \"*-format*:*\") and\n not wildcard(process.command_line, \"* /format:table *\")]\n[library where event.type == \"start\" and file.name in (\"jscript.dll\", \"vbscript.dll\")]\n", + "query": "sequence by process.entity_id with maxspan = 2m\n[process where event.type in (\"start\", \"process_started\") and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name : \"wmic.exe\") and\n process.args : (\"format*:*\", \"/format*:*\", \"*-format*:*\") and\n not process.command_line : \"* /format:table *\"]\n[library where event.type == \"start\" and dll.name : (\"jscript.dll\", \"vbscript.dll\")]\n", "risk_score": 21, "rule_id": "7f370d54-c0eb-4270-ac5a-9a6020585dc6", "severity": "medium", @@ -39,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json index 6abe5b5ee4c400..571f42cf0e942a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json index 265f648c7959dc..811dae6b7ce534 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json new file mode 100644 index 00000000000000..d79c90e703ef73 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the mount_apfs command to mount the entire file system through Apple File System (APFS) snapshots as read-only and with the noowners flag set. This action enables the adversary to access almost any file in the file system, including all user data and files protected by Apple\u2019s privacy framework (TCC).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "TCC Bypass via Mounted APFS Snapshot Access", + "query": "event.category : process and event.type : (start or process_started) and process.name : mount_apfs and process.args : (/System/Volumes/Data and noowners)", + "references": [ + "https://theevilbit.github.io/posts/cve_2020_9771/" + ], + "risk_score": 73, + "rule_id": "b00bcd89-000c-4425-b94c-716ef67762f6", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1006", + "name": "Direct Volume Access", + "reference": "https://attack.mitre.org/techniques/T1006/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json index fe8268d11cc2d7..e6275668be487c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json @@ -12,7 +12,7 @@ "license": "Elastic License", "max_signals": 33, "name": "Timestomping using Touch Command", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.name == \"touch\" and wildcard(process.args, \"-r\", \"-t\", \"-a*\",\"-m*\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : \"touch\" and process.args : (\"-r\", \"-t\", \"-a*\",\"-m*\")\n", "risk_score": 47, "rule_id": "b0046934-486e-462f-9487-0d4cf9e429c6", "severity": "medium", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json new file mode 100644 index 00000000000000..9725619d5b3996 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to unload the Elastic Endpoint Security kernel extension via the kextunload command.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Unload Elastic Endpoint Security Kernel Extension", + "query": "event.category:process and event.type:(start or process_started) and process.name:kextunload and process.args:(\"/System/Library/Extensions/EndpointSecurity.kext\" or \"EndpointSecurity.kext\")", + "risk_score": 73, + "rule_id": "70fa1af4-27fd-4f26-bd03-50b6af6b9e24", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json new file mode 100644 index 00000000000000..4afbf0472f0258 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious creation of Alternate Data Streams on highly targeted files. This is uncommon for legitimate files and sometimes done by adversaries to hide malware.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Unusual File Creation - Alternate Data Stream", + "query": "file where event.type == \"creation\" and\n file.path : \"C:\\\\*:*\" and\n not file.path : \"C:\\\\*:zone.identifier*\" and\n file.extension :\n (\n \"pdf\",\n \"dll\",\n \"png\",\n \"exe\",\n \"dat\",\n \"com\",\n \"bat\",\n \"cmd\",\n \"sys\",\n \"vbs\",\n \"ps1\",\n \"hta\",\n \"txt\",\n \"vbe\",\n \"js\",\n \"wsh\",\n \"docx\",\n \"doc\",\n \"xlsx\",\n \"xls\",\n \"pptx\",\n \"ppt\",\n \"rtf\",\n \"gif\",\n \"jpg\",\n \"png\",\n \"bmp\",\n \"img\",\n \"iso\"\n )\n", + "risk_score": 47, + "rule_id": "71bccb61-e19b-452f-b104-79a60e546a95", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.004", + "name": "NTFS File Attributes", + "reference": "https://attack.mitre.org/techniques/T1564/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json index 3327afd89f541e..ed10ddf4a4fb7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies processes running from an Alternate Data Stream. This is uncommon for legitimate processes and sometimes done by adversaries to hide malware.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json index 8c885aa52be593..19665527ccabb6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json index bd8ac27521a439..77b932b97c88f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json index 819c85a1ade628..f20998dea964ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json index fc7a66be016ef4..797a2bad31c4c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Filter Manager Control Program (fltMC.exe) binary may be abused by adversaries to unload a filter driver and evade defenses.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json index b41ad15ca3e17d..11623d61fa6972 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json index 6bdc8a65154e31..64483156611776 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -74,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json index 57c9ba77385c11..355b5df097a13f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies instances of lower privilege accounts enumerating Administrator accounts or groups using built-in Windows tools.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json index c6ed7328701953..a4e76120504cc7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json @@ -3,14 +3,19 @@ "Elastic" ], "description": "Enumeration of files and directories using built-in tools. Adversaries may use the information discovered to plan follow-on activity.", + "false_positives": [ + "Enumeration of files and directories may not be inherently malicious and noise may come from scripts, automation tools, or normal command line usage. It's important to baseline your environment to determine the amount of expected noise and exclude any known FP's from the rule." + ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "File and Directory Discovery", - "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and\n process.args : (\"dir\", \"tree\")\n", + "query": "sequence by agent.id, user.name with maxspan=1m\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n", "risk_score": 21, "rule_id": "7b08314d-47a0-4b71-ae4e-16544176924f", "severity": "low", @@ -40,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json index 5572178361a09d..25cdc2bd1c44ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json index 0f4be41389cbca..1cb6364a323f57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies attempts to enumerate hosts in a network using the built-in Windows net.exe tool.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json index 005a7d34ba7183..467ce488252a1e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies use of the Windows file system utility (fsutil.exe ) to gather information about attached peripheral devices and components connected to a computer system.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json index af28fe75f525f6..32a434c1a9a062 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json @@ -6,9 +6,11 @@ "false_positives": [ "Administrators may use the tasklist command to display a list of currently running processes. By itself, it does not indicate malicious activity. After obtaining a foothold, it's possible adversaries may use discovery commands like tasklist to get information about running processes." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json index a04e97e2fa9f6f..1933ec66c866f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Enumeration or discovery of the Windows registry using reg.exe. This information can be used to perform follow-on activities.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json index 24e3bd87c526dd..8f8ccf5943061f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Discovery of remote system information using built-in commands, which may be used to mover laterally.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json new file mode 100644 index 00000000000000..d211fad332da67 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the grep command to discover known third-party macOS and Linux security tools, such as Antivirus or Host Firewall details.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Security Software Discovery via Grep", + "query": "event.category : process and event.type : (start or process_started) and process.name : grep and process.args : (\"Little Snitch\" or Avast* or Avira* or ESET* or esets_* or BlockBlock or 360* or LuLu or KnockKnock* or kav or KIS or RTProtectionDaemon or Malware* or VShieldScanner or WebProtection or webinspectord or McAfee* or isecespd* or macmnsvc* or masvc or kesl or avscan or guard or rtvscand or symcfgd or scmdaemon or symantec or elastic-endpoint )", + "risk_score": 47, + "rule_id": "870aecc0-cea4-4110-af3f-e02e9b373655", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1518", + "name": "Software Discovery", + "reference": "https://attack.mitre.org/techniques/T1518/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json index 51d4425f7d5382..89c6390856e926 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json new file mode 100644 index 00000000000000..928585fb59968d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands related to account or group enumeration.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Enumeration of Users or Groups via Built-in Commands", + "query": "process where event.type in (\"start\", \"process_started\") and\n not process.parent.executable : (\"/Applications/NoMAD.app/Contents/MacOS/NoMAD\", \n \"/Applications/ZoomPresence.app/Contents/MacOS/ZoomPresence\") and \n process.name : (\"ldapsearch\", \"dsmemberutil\") or\n (process.name : \"dscl\" and \n process.args : (\"read\", \"-read\", \"list\", \"-list\", \"ls\", \"search\", \"-search\") and \n process.args : (\"/Active Directory/*\", \"/Users*\", \"/Groups*\"))\n", + "risk_score": 21, + "rule_id": "6e9b351e-a531-4bdc-b73e-7034d6eed7ff", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1069", + "name": "Permission Groups Discovery", + "reference": "https://attack.mitre.org/techniques/T1069/" + }, + { + "id": "T1087", + "name": "Account Discovery", + "reference": "https://attack.mitre.org/techniques/T1087/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json index bb6810f5437d32..2ed21b39f8246e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json @@ -6,9 +6,11 @@ "false_positives": [ "Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools and frameworks. Usage by non-engineers and ordinary users is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json index 6cf46e35595de5..8084067b3a6d28 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json index 69ba61dea32287..9c28d065b322d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json index 323ad5c0f446b7..352712e38f42de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json index 9ca5a3da5ae213..259bcd51aeb3e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json index 4923e42d16d9c5..19348062b10f1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json index 9c9fcc559bea7f..2fd3aaa0d8a576 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json index 50861dba7f3fb4..8f90e1162546b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json index 72d52a97273207..3d740f8b7064f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json index 7af172d90eb30c..33195c7fcbecca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json index 28f473795299a5..fac13a6d358dd2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json index 09c9f83f95622b..a2d8700076c234 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json index af235ea2022cf1..ef4f29067b0c5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json index cd16caf11482b1..b22751e35c053d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json index 8353cc06972e21..3b973f42bbca52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json index 590c6b08140677..b6458b73e80153 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json index 8786510ee5cb79..a60122ef6c1a29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json index fc12a48e3f5a16..04f204a4596b35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json new file mode 100644 index 00000000000000..182704d149e48e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows Component Object Model (COM) is an inter-process communication (IPC) component of the native Windows application programming interface (API) that enables interaction between software objects or executable code. Xwizard can be used to run a COM object created in registry to evade defensive counter measures.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Execution of COM object via Xwizard", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name : \"xwizard.exe\" and\n (\n (process.args : \"RunWizard\" and process.args : \"{*}\") or\n (process.executable != null and\n not process.executable : (\"C:\\\\Windows\\\\SysWOW64\\\\xwizard.exe\", \"C:\\\\Windows\\\\System32\\\\xwizard.exe\")\n )\n )\n", + "references": [ + "https://lolbas-project.github.io/lolbas/Binaries/Xwizard/", + "http://www.hexacorn.com/blog/2017/07/31/the-wizard-of-x-oppa-plugx-style/" + ], + "risk_score": 47, + "rule_id": "1a6075b0-7479-450e-8fe7-b8b8438ac570", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1559", + "name": "Inter-Process Communication", + "reference": "https://attack.mitre.org/techniques/T1559/", + "subtechnique": [ + { + "id": "T1559.001", + "name": "Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1559/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json index 12d2a94afc823a..e038ed56ec1759 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -58,5 +59,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json index aa0632c5614f6e..16b964b6e95888 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json index d5042ee5d64fd3..cde62236effe03 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json index 90b3759d93de9f..981fdb5a4f7f20 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json index 45ee672c1d635c..641abc92b1204f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json new file mode 100644 index 00000000000000..eb80782fe2495b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json @@ -0,0 +1,66 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to execute a child process from within the context of an Electron application using the child_process Node.js module. Adversaries may abuse this technique to inherit permissions from parent processes.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Execution via Electron Child Process Node.js Module", + "query": "event.category:process and event.type:(start or process_started) and process.args:(\"-e\" and const*require*child_process*)", + "references": [ + "https://www.matthewslipper.com/2019/09/22/everything-you-wanted-electron-child-process.html", + "https://www.trustedsec.com/blog/macos-injection-via-third-party-frameworks/", + "https://nodejs.org/api/child_process.html" + ], + "risk_score": 47, + "rule_id": "35330ba2-c859-4c98-8b7f-c19159ea0e58", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json new file mode 100644 index 00000000000000..0fe7321ebe4b36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies native Windows host and network enumeration commands spawned by the Windows Management Instrumentation Provider Service (WMIPrvSE).", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Enumeration Command Spawned via WMIPrvSE", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name:\n (\n \"arp.exe\",\n \"dsquery.exe\",\n \"dsget.exe\",\n \"gpresult.exe\",\n \"hostname.exe\",\n \"ipconfig.exe\",\n \"nbtstat.exe\",\n \"net.exe\",\n \"net1.exe\",\n \"netsh.exe\",\n \"netstat.exe\",\n \"nltest.exe\",\n \"ping.exe\",\n \"qprocess.exe\",\n \"quser.exe\",\n \"qwinsta.exe\",\n \"reg.exe\",\n \"sc.exe\",\n \"systeminfo.exe\",\n \"tasklist.exe\",\n \"tracert.exe\",\n \"whoami.exe\"\n ) and\n process.parent.name:\"wmiprvse.exe\"\n", + "risk_score": 21, + "rule_id": "770e0c4d-b998-41e5-a62e-c7901fd7f470", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1047", + "name": "Windows Management Instrumentation", + "reference": "https://attack.mitre.org/techniques/T1047/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json index 43166722e6fc06..a6bc33e4b2e735 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -24,5 +25,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json index 2663b97bd91514..63adf0a602e2c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Execution from Unusual Directory - Command Line", "note": "This is related to the Process Execution from an Unusual Directory rule", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name : (\"wscript.exe\",\"cscript.exe\",\"rundll32.exe\",\"regsvr32.exe\",\"cmstp.exe\",\"RegAsm.exe\",\"installutil.exe\",\"mshta.exe\",\"RegSvcs.exe\") and\n /* add suspicious execution paths here */\nprocess.args : (\"C:\\\\PerfLogs\\\\*\",\"C:\\\\Users\\\\Public\\\\*\",\"C:\\\\Users\\\\Default\\\\*\",\"C:\\\\Windows\\\\Tasks\\\\*\",\"C:\\\\Intel\\\\*\", \"C:\\\\AMD\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\AppReadiness\\\\*\", \"C:\\\\Windows\\\\ServiceState\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\IdentityCRL\\\\*\",\"C:\\\\Windows\\\\Branding\\\\*\",\"C:\\\\Windows\\\\csc\\\\*\",\n \"C:\\\\Windows\\\\DigitalLocker\\\\*\",\"C:\\\\Windows\\\\en-US\\\\*\",\"C:\\\\Windows\\\\wlansvc\\\\*\",\"C:\\\\Windows\\\\Prefetch\\\\*\",\"C:\\\\Windows\\\\Fonts\\\\*\",\n \"C:\\\\Windows\\\\diagnostics\\\\*\",\"C:\\\\Windows\\\\TAPI\\\\*\",\"C:\\\\Windows\\\\INF\\\\*\",\"C:\\\\Windows\\\\System32\\\\Speech\\\\*\",\"C:\\\\windows\\\\tracing\\\\*\",\n \"c:\\\\windows\\\\IME\\\\*\",\"c:\\\\Windows\\\\Performance\\\\*\",\"c:\\\\windows\\\\intel\\\\*\",\"c:\\\\windows\\\\ms\\\\*\",\"C:\\\\Windows\\\\dot3svc\\\\*\",\"C:\\\\Windows\\\\ServiceProfiles\\\\*\",\n \"C:\\\\Windows\\\\panther\\\\*\",\"C:\\\\Windows\\\\RemotePackages\\\\*\",\"C:\\\\Windows\\\\OCR\\\\*\",\"C:\\\\Windows\\\\appcompat\\\\*\",\"C:\\\\Windows\\\\apppatch\\\\*\",\"C:\\\\Windows\\\\addins\\\\*\",\n \"C:\\\\Windows\\\\Setup\\\\*\",\"C:\\\\Windows\\\\Help\\\\*\",\"C:\\\\Windows\\\\SKB\\\\*\",\"C:\\\\Windows\\\\Vss\\\\*\",\"C:\\\\Windows\\\\Web\\\\*\",\"C:\\\\Windows\\\\servicing\\\\*\",\"C:\\\\Windows\\\\CbsTemp\\\\*\",\n \"C:\\\\Windows\\\\Logs\\\\*\",\"C:\\\\Windows\\\\WaaS\\\\*\",\"C:\\\\Windows\\\\twain_32\\\\*\",\"C:\\\\Windows\\\\ShellExperiences\\\\*\",\"C:\\\\Windows\\\\ShellComponents\\\\*\",\"C:\\\\Windows\\\\PLA\\\\*\",\n \"C:\\\\Windows\\\\Migration\\\\*\",\"C:\\\\Windows\\\\debug\\\\*\",\"C:\\\\Windows\\\\Cursors\\\\*\",\"C:\\\\Windows\\\\Containers\\\\*\",\"C:\\\\Windows\\\\Boot\\\\*\",\"C:\\\\Windows\\\\bcastdvr\\\\*\",\n \"C:\\\\Windows\\\\assembly\\\\*\",\"C:\\\\Windows\\\\TextInput\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\schemas\\\\*\",\"C:\\\\Windows\\\\SchCache\\\\*\",\"C:\\\\Windows\\\\Resources\\\\*\",\n \"C:\\\\Windows\\\\rescache\\\\*\",\"C:\\\\Windows\\\\Provisioning\\\\*\",\"C:\\\\Windows\\\\PrintDialog\\\\*\",\"C:\\\\Windows\\\\PolicyDefinitions\\\\*\",\"C:\\\\Windows\\\\media\\\\*\",\n \"C:\\\\Windows\\\\Globalization\\\\*\",\"C:\\\\Windows\\\\L2Schemas\\\\*\",\"C:\\\\Windows\\\\LiveKernelReports\\\\*\",\"C:\\\\Windows\\\\ModemLogs\\\\*\",\"C:\\\\Windows\\\\ImmersiveControlPanel\\\\*\")\n", + "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name : (\"wscript.exe\",\"cscript.exe\",\"rundll32.exe\",\"regsvr32.exe\",\"cmstp.exe\",\"RegAsm.exe\",\"installutil.exe\",\"mshta.exe\",\"RegSvcs.exe\", \"powershell.exe\", \"pwsh.exe\", \"cmd.exe\") and\n /* add suspicious execution paths here */\nprocess.args : (\"C:\\\\PerfLogs\\\\*\",\"C:\\\\Users\\\\Public\\\\*\",\"C:\\\\Users\\\\Default\\\\*\",\"C:\\\\Windows\\\\Tasks\\\\*\",\"C:\\\\Intel\\\\*\", \"C:\\\\AMD\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\AppReadiness\\\\*\", \"C:\\\\Windows\\\\ServiceState\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\IdentityCRL\\\\*\",\"C:\\\\Windows\\\\Branding\\\\*\",\"C:\\\\Windows\\\\csc\\\\*\",\n \"C:\\\\Windows\\\\DigitalLocker\\\\*\",\"C:\\\\Windows\\\\en-US\\\\*\",\"C:\\\\Windows\\\\wlansvc\\\\*\",\"C:\\\\Windows\\\\Prefetch\\\\*\",\"C:\\\\Windows\\\\Fonts\\\\*\",\n \"C:\\\\Windows\\\\diagnostics\\\\*\",\"C:\\\\Windows\\\\TAPI\\\\*\",\"C:\\\\Windows\\\\INF\\\\*\",\"C:\\\\Windows\\\\System32\\\\Speech\\\\*\",\"C:\\\\windows\\\\tracing\\\\*\",\n \"c:\\\\windows\\\\IME\\\\*\",\"c:\\\\Windows\\\\Performance\\\\*\",\"c:\\\\windows\\\\intel\\\\*\",\"c:\\\\windows\\\\ms\\\\*\",\"C:\\\\Windows\\\\dot3svc\\\\*\",\"C:\\\\Windows\\\\ServiceProfiles\\\\*\",\n \"C:\\\\Windows\\\\panther\\\\*\",\"C:\\\\Windows\\\\RemotePackages\\\\*\",\"C:\\\\Windows\\\\OCR\\\\*\",\"C:\\\\Windows\\\\appcompat\\\\*\",\"C:\\\\Windows\\\\apppatch\\\\*\",\"C:\\\\Windows\\\\addins\\\\*\",\n \"C:\\\\Windows\\\\Setup\\\\*\",\"C:\\\\Windows\\\\Help\\\\*\",\"C:\\\\Windows\\\\SKB\\\\*\",\"C:\\\\Windows\\\\Vss\\\\*\",\"C:\\\\Windows\\\\Web\\\\*\",\"C:\\\\Windows\\\\servicing\\\\*\",\"C:\\\\Windows\\\\CbsTemp\\\\*\",\n \"C:\\\\Windows\\\\Logs\\\\*\",\"C:\\\\Windows\\\\WaaS\\\\*\",\"C:\\\\Windows\\\\twain_32\\\\*\",\"C:\\\\Windows\\\\ShellExperiences\\\\*\",\"C:\\\\Windows\\\\ShellComponents\\\\*\",\"C:\\\\Windows\\\\PLA\\\\*\",\n \"C:\\\\Windows\\\\Migration\\\\*\",\"C:\\\\Windows\\\\debug\\\\*\",\"C:\\\\Windows\\\\Cursors\\\\*\",\"C:\\\\Windows\\\\Containers\\\\*\",\"C:\\\\Windows\\\\Boot\\\\*\",\"C:\\\\Windows\\\\bcastdvr\\\\*\",\n \"C:\\\\Windows\\\\assembly\\\\*\",\"C:\\\\Windows\\\\TextInput\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\schemas\\\\*\",\"C:\\\\Windows\\\\SchCache\\\\*\",\"C:\\\\Windows\\\\Resources\\\\*\",\n \"C:\\\\Windows\\\\rescache\\\\*\",\"C:\\\\Windows\\\\Provisioning\\\\*\",\"C:\\\\Windows\\\\PrintDialog\\\\*\",\"C:\\\\Windows\\\\PolicyDefinitions\\\\*\",\"C:\\\\Windows\\\\media\\\\*\",\n \"C:\\\\Windows\\\\Globalization\\\\*\",\"C:\\\\Windows\\\\L2Schemas\\\\*\",\"C:\\\\Windows\\\\LiveKernelReports\\\\*\",\"C:\\\\Windows\\\\ModemLogs\\\\*\",\"C:\\\\Windows\\\\ImmersiveControlPanel\\\\*\",\n \"C:\\\\$Recycle.Bin\\\\*\")\n", "risk_score": 47, "rule_id": "cff92c41-2225-4763-b4ce-6f71e5bda5e6", "severity": "medium", @@ -25,5 +26,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json index b85e74c8546368..9cf6ee67a06ccc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -56,5 +57,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json new file mode 100644 index 00000000000000..61e770b25290d0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a suspicious browser child process. Adversaries may gain access to a system through a user visiting a website over the normal course of browsing. With this technique, the user's web browser is typically targeted for exploitation.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Browser Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"Google Chrome\", \"Google Chrome Helper*\", \"firefox\", \"Opera\", \"Safari\", \"com.apple.WebKit.WebContent\", \"Microsoft Edge\") and\n process.name : (\"sh\", \"bash\", \"dash\", \"ksh\", \"tcsh\", \"zsh\", \"curl\", \"wget\", \"python*\", \"perl*\", \"php*\", \"osascript\", \"pwsh\") and \n process.command_line != null and \n not process.args : \n ( \n \"/Library/Application Support/Microsoft/MAU*/Microsoft AutoUpdate.app/Contents/MacOS/msupdate\", \n \"hw.model\", \n \"IOPlatformExpertDevice\", \n \"/Volumes/Google Chrome/Google Chrome.app/Contents/Frameworks/*/Resources/install.sh\",\n \"--defaults-torrc\", \n \"Chrome.app\", \n \"Framework.framework/Versions/*/Resources/keystone_promote_preflight.sh\", \n \"/Users/*/Library/Application Support/Google/Chrome/recovery/*/ChromeRecovery\", \n \"$DISPLAY\", \n \"GIO_LAUNCHED_DESKTOP_FILE_PID=$$\"\n )\n", + "references": [ + "https://objective-see.com/blog/blog_0x43.html", + "https://fr.slideshare.net/codeblue_jp/cb19-recent-apt-attack-on-crypto-exchange-employees-by-heungsoo-kang" + ], + "risk_score": 73, + "rule_id": "080bc66a-5d56-4d1f-8071-817671716db9", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Initial Access", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1203", + "name": "Exploitation for Client Execution", + "reference": "https://attack.mitre.org/techniques/T1203/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1189", + "name": "Drive-by Compromise", + "reference": "https://attack.mitre.org/techniques/T1189/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json index cf1fbc4ba0ba2a..dee13e29dfebcb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -61,5 +62,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json index f0d32cf8882bba..a49e225777ca77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -61,5 +62,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json new file mode 100644 index 00000000000000..b86a5c1d25bdd2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json @@ -0,0 +1,32 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of and EggShell Backdoor. EggShell is a known post exploitation tool for macOS and Linux.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "EggShell Backdoor Execution", + "query": "event.category:process and event.type:(start or process_started) and process.name:espl and process.args:eyJkZWJ1ZyI6*", + "references": [ + "https://github.com/neoneggplant/EggShell" + ], + "risk_score": 73, + "rule_id": "41824afb-d68c-4d0e-bfee-474dac1fa56e", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json index 00a63dded94c6a..ef219d1aa6c75a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json index 4a5defb4f42a41..21672969ccf8fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json new file mode 100644 index 00000000000000..12a8a27bb65d5f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a shell process with suspicious arguments which may be indicative of reverse shell activity.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Reverse Shell Activity via Terminal", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name in (\"sh\", \"bash\", \"zsh\", \"dash\", \"zmodload\") and\n process.args:(\"*/dev/tcp/*\", \"*/dev/udp/*\", \"zsh/net/tcp\", \"zsh/net/udp\")\n", + "references": [ + "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md", + "https://github.com/WangYihang/Reverse-Shell-Manager", + "https://www.netsparker.com/blog/web-security/understanding-reverse-shells/" + ], + "risk_score": 73, + "rule_id": "a1a0375f-22c2-48c0-81a4-7c2d11cc6856", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json index 3c7e0d00be9078..9ace0fe4e2f8e5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json @@ -9,12 +9,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Outbound Scheduled Task Activity via PowerShell", - "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [library where file.name: \"taskschd.dll\" and process.name: (\"powershell.exe\", \"pwsh.exe\")]\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\") and destination.port == 135 and not destination.address in (\"127.0.0.1\", \"::1\")]\n", + "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [library where dll.name : \"taskschd.dll\" and process.name : (\"powershell.exe\", \"pwsh.exe\")]\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\") and destination.port == 135 and not destination.address in (\"127.0.0.1\", \"::1\")]\n", "references": [ "https://www.volexity.com/blog/2020/12/14/dark-halo-leverages-solarwinds-compromise-to-breach-organizations/" ], @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json new file mode 100644 index 00000000000000..8796a3edcc3459 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json @@ -0,0 +1,47 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of the Automator Workflows process followed by a network connection from it's XPC service. Adversaries may drop a custom workflow template that hosts malicious JavaScript for Automation (JXA) code as an alternative to using osascript.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Automator Workflows Execution", + "query": "sequence by host.id with maxspan=30s\n [process where event.type in (\"start\", \"process_started\") and process.name == \"automator\"]\n [network where process.name:\"com.apple.automator.runner\"]\n", + "references": [ + "https://posts.specterops.io/persistent-jxa-66e1c3cd1cf5" + ], + "risk_score": 47, + "rule_id": "5d9f8cfc-0d03-443e-a167-2b0597ce0965", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json index 98a11faa076d4f..d3d8715d29be09 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -16,7 +17,7 @@ "references": [ "https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "a3ea12f3-0d4e-4667-8b44-4230c63f3c75", "severity": "medium", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json index 7c4480b5e9c574..8f6e2d6eca71b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Cmd Execution via WMI", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name == \"WmiPrvSE.exe\" and process.name == \"cmd.exe\" and\n wildcard(process.args, \"\\\\\\\\127.0.0.1\\\\*\") and process.args in (\"2>&1\", \"1>\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"WmiPrvSE.exe\" and process.name : \"cmd.exe\" and\n process.args : \"\\\\\\\\127.0.0.1\\\\*\" and process.args : (\"2>&1\", \"1>\")\n", "risk_score": 47, "rule_id": "12f07955-1674-44f7-86b5-c35da0a6f41a", "severity": "medium", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json index aabeb4fb75ab59..79ecde48e5bda1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json @@ -3,14 +3,16 @@ "Elastic" ], "description": "Identifies a suspicious image load (wmiutils.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where child processes are spawned via Windows Management Instrumentation (WMI). This technique can be used to execute code and evade traditional parent/child processes spawned from MS Office products.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious WMI Image Load from MS Office", - "query": "library where process.name in (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action == \"load\" and\n event.category == \"library\" and\n file.name == \"wmiutils.dll\"\n", + "query": "library where process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action : \"load\" and\n event.category : \"library\" and\n dll.name : \"wmiutils.dll\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16" ], @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json new file mode 100644 index 00000000000000..b32c66bbc1dbf0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious child processes of a Java Archive (JAR) file. JAR files may be used to deliver malware in order to evade detection.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious JAR Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"java\" and\n process.name : (\"sh\", \"bash\", \"dash\", \"ksh\", \"tcsh\", \"zsh\", \"curl\", \"wget\") and\n process.args : \"-jar\" and process.args : \"*.jar\" and\n /* Add any FP's here */\n not process.executable : (\"/Users/*/.sdkman/*\", \"/Library/Java/JavaVirtualMachines/*\") and\n not process.args : (\"/usr/local/*\", \"/Users/*/github.com/*\", \"/Users/*/src/*\")\n", + "risk_score": 47, + "rule_id": "8acb7614-1d92-4359-bfcf-478b6d9de150", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.007", + "name": "JavaScript/JScript", + "reference": "https://attack.mitre.org/techniques/T1059/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json index 01096ce781eb1f..5a6d440d7e081c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json index bd25919944e1f8..c21848f01080a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious PowerShell Engine ImageLoad", - "query": "library where file.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") and\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\", \"C:\\\\Program Files*\\\\*.exe\") and\n not process.name : (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", + "query": "library where dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") and\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\", \"C:\\\\Program Files*\\\\*.exe\") and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", "risk_score": 47, "rule_id": "852c1f19-68e8-43a6-9dce-340771fe1be3", "severity": "medium", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json index 25ac9815ebbb89..1889310e10df8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json index 58236ba2023415..00ca1613f1c9bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -24,5 +25,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json index 28fedc8e7bc3a1..98ffa167166d4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json @@ -6,9 +6,11 @@ "false_positives": [ "The HTML Help executable program (hh.exe) runs whenever a user clicks a compiled help (.chm) file or menu item that opens the help file inside the Help Viewer. This is not always malicious, but adversaries may abuse this technology to conceal malicious code." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -59,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json index 289513bdaf562b..edf29080bf08e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json index 5209c30dc33e69..98b6d40d233726 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json index e64ee320373ada..5e7ac017e1c912 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json index 475c4f4937a54b..6d3a7bf9592fa8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json @@ -7,7 +7,8 @@ "index": [ "auditbeat-*", "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -55,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json index 3f772a80e680b4..ec69bd9ceab25d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index c59b2942a8d636..87e5378aaf315f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -10,467 +10,548 @@ // - detection-rules repo using CLI command build-release // Do not hand edit. Run script/command to regenerate package information instead -import rule1 from './apm_403_response_to_a_post.json'; -import rule2 from './apm_405_response_method_not_allowed.json'; -import rule3 from './apm_null_user_agent.json'; -import rule4 from './apm_sqlmap_user_agent.json'; -import rule5 from './command_and_control_dns_directly_to_the_internet.json'; -import rule6 from './command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json'; -import rule7 from './command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; -import rule8 from './command_and_control_nat_traversal_port_activity.json'; -import rule9 from './command_and_control_port_26_activity.json'; -import rule10 from './command_and_control_port_8000_activity_to_the_internet.json'; -import rule11 from './command_and_control_pptp_point_to_point_tunneling_protocol_activity.json'; -import rule12 from './command_and_control_proxy_port_activity_to_the_internet.json'; -import rule13 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule14 from './command_and_control_smtp_to_the_internet.json'; -import rule15 from './command_and_control_sql_server_port_activity_to_the_internet.json'; -import rule16 from './command_and_control_ssh_secure_shell_from_the_internet.json'; -import rule17 from './command_and_control_ssh_secure_shell_to_the_internet.json'; -import rule18 from './command_and_control_telnet_port_activity.json'; -import rule19 from './command_and_control_tor_activity_to_the_internet.json'; -import rule20 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; -import rule21 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; -import rule22 from './credential_access_tcpdump_activity.json'; -import rule23 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; -import rule24 from './defense_evasion_clearing_windows_event_logs.json'; -import rule25 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; -import rule26 from './defense_evasion_deleting_backup_catalogs_with_wbadmin.json'; -import rule27 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; -import rule28 from './defense_evasion_encoding_or_decoding_files_via_certutil.json'; -import rule29 from './defense_evasion_execution_via_trusted_developer_utilities.json'; -import rule30 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; -import rule31 from './defense_evasion_msbuild_making_network_connections.json'; -import rule32 from './defense_evasion_unusual_network_connection_via_rundll32.json'; -import rule33 from './defense_evasion_unusual_process_network_connection.json'; -import rule34 from './defense_evasion_via_filter_manager.json'; -import rule35 from './defense_evasion_volume_shadow_copy_deletion_via_wmic.json'; -import rule36 from './discovery_process_discovery_via_tasklist_command.json'; -import rule37 from './discovery_whoami_command_activity.json'; -import rule38 from './discovery_whoami_commmand.json'; -import rule39 from './endpoint_adversary_behavior_detected.json'; -import rule40 from './endpoint_cred_dumping_detected.json'; -import rule41 from './endpoint_cred_dumping_prevented.json'; -import rule42 from './endpoint_cred_manipulation_detected.json'; -import rule43 from './endpoint_cred_manipulation_prevented.json'; -import rule44 from './endpoint_exploit_detected.json'; -import rule45 from './endpoint_exploit_prevented.json'; -import rule46 from './endpoint_malware_detected.json'; -import rule47 from './endpoint_malware_prevented.json'; -import rule48 from './endpoint_permission_theft_detected.json'; -import rule49 from './endpoint_permission_theft_prevented.json'; -import rule50 from './endpoint_process_injection_detected.json'; -import rule51 from './endpoint_process_injection_prevented.json'; -import rule52 from './endpoint_ransomware_detected.json'; -import rule53 from './endpoint_ransomware_prevented.json'; -import rule54 from './execution_command_prompt_connecting_to_the_internet.json'; -import rule55 from './execution_command_shell_started_by_powershell.json'; -import rule56 from './execution_command_shell_started_by_svchost.json'; -import rule57 from './execution_html_help_executable_program_connecting_to_the_internet.json'; -import rule58 from './execution_psexec_lateral_movement_command.json'; -import rule59 from './execution_register_server_program_connecting_to_the_internet.json'; -import rule60 from './execution_via_compiled_html_file.json'; -import rule61 from './impact_volume_shadow_copy_deletion_via_vssadmin.json'; -import rule62 from './initial_access_rdp_remote_desktop_protocol_to_the_internet.json'; -import rule63 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; -import rule64 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; -import rule65 from './initial_access_script_executing_powershell.json'; -import rule66 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule67 from './initial_access_suspicious_ms_office_child_process.json'; -import rule68 from './initial_access_suspicious_ms_outlook_child_process.json'; -import rule69 from './lateral_movement_direct_outbound_smb_connection.json'; -import rule70 from './lateral_movement_local_service_commands.json'; -import rule71 from './linux_hping_activity.json'; -import rule72 from './linux_iodine_activity.json'; -import rule73 from './linux_mknod_activity.json'; -import rule74 from './linux_netcat_network_connection.json'; -import rule75 from './linux_nmap_activity.json'; -import rule76 from './linux_nping_activity.json'; -import rule77 from './linux_process_started_in_temp_directory.json'; -import rule78 from './linux_socat_activity.json'; -import rule79 from './linux_strace_activity.json'; -import rule80 from './persistence_adobe_hijack_persistence.json'; -import rule81 from './persistence_kernel_module_activity.json'; -import rule82 from './persistence_local_scheduled_task_commands.json'; -import rule83 from './persistence_priv_escalation_via_accessibility_features.json'; -import rule84 from './persistence_shell_activity_by_web_server.json'; -import rule85 from './persistence_system_shells_via_services.json'; -import rule86 from './persistence_user_account_creation.json'; -import rule87 from './persistence_via_application_shimming.json'; -import rule88 from './privilege_escalation_unusual_parentchild_relationship.json'; -import rule89 from './defense_evasion_modification_of_boot_config.json'; -import rule90 from './privilege_escalation_uac_bypass_event_viewer.json'; -import rule91 from './defense_evasion_msxsl_network.json'; -import rule92 from './discovery_net_command_system_account.json'; -import rule93 from './command_and_control_certutil_network_connection.json'; -import rule94 from './defense_evasion_cve_2020_0601.json'; -import rule95 from './credential_access_credential_dumping_msbuild.json'; -import rule96 from './defense_evasion_execution_msbuild_started_by_office_app.json'; -import rule97 from './defense_evasion_execution_msbuild_started_by_script.json'; -import rule98 from './defense_evasion_execution_msbuild_started_by_system_process.json'; -import rule99 from './defense_evasion_execution_msbuild_started_renamed.json'; -import rule100 from './defense_evasion_execution_msbuild_started_unusal_process.json'; -import rule101 from './defense_evasion_injection_msbuild.json'; -import rule102 from './execution_via_net_com_assemblies.json'; -import rule103 from './ml_linux_anomalous_network_activity.json'; -import rule104 from './ml_linux_anomalous_network_port_activity.json'; -import rule105 from './ml_linux_anomalous_network_service.json'; -import rule106 from './ml_linux_anomalous_network_url_activity.json'; -import rule107 from './ml_linux_anomalous_process_all_hosts.json'; -import rule108 from './ml_linux_anomalous_user_name.json'; -import rule109 from './ml_packetbeat_dns_tunneling.json'; -import rule110 from './ml_packetbeat_rare_dns_question.json'; -import rule111 from './ml_packetbeat_rare_server_domain.json'; -import rule112 from './ml_packetbeat_rare_urls.json'; -import rule113 from './ml_packetbeat_rare_user_agent.json'; -import rule114 from './ml_rare_process_by_host_linux.json'; -import rule115 from './ml_rare_process_by_host_windows.json'; -import rule116 from './ml_suspicious_login_activity.json'; -import rule117 from './ml_windows_anomalous_network_activity.json'; -import rule118 from './ml_windows_anomalous_path_activity.json'; -import rule119 from './ml_windows_anomalous_process_all_hosts.json'; -import rule120 from './ml_windows_anomalous_process_creation.json'; -import rule121 from './ml_windows_anomalous_script.json'; -import rule122 from './ml_windows_anomalous_service.json'; -import rule123 from './ml_windows_anomalous_user_name.json'; -import rule124 from './ml_windows_rare_user_runas_event.json'; -import rule125 from './ml_windows_rare_user_type10_remote_login.json'; -import rule126 from './execution_suspicious_pdf_reader.json'; -import rule127 from './privilege_escalation_sudoers_file_mod.json'; -import rule128 from './defense_evasion_iis_httplogging_disabled.json'; -import rule129 from './execution_python_tty_shell.json'; -import rule130 from './execution_perl_tty_shell.json'; -import rule131 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; -import rule132 from './defense_evasion_base64_encoding_or_decoding_activity.json'; -import rule133 from './defense_evasion_hex_encoding_or_decoding_activity.json'; -import rule134 from './defense_evasion_file_mod_writable_dir.json'; -import rule135 from './defense_evasion_disable_selinux_attempt.json'; -import rule136 from './discovery_kernel_module_enumeration.json'; -import rule137 from './lateral_movement_telnet_network_activity_external.json'; -import rule138 from './lateral_movement_telnet_network_activity_internal.json'; -import rule139 from './privilege_escalation_setgid_bit_set_via_chmod.json'; -import rule140 from './privilege_escalation_setuid_bit_set_via_chmod.json'; -import rule141 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; -import rule142 from './defense_evasion_kernel_module_removal.json'; -import rule143 from './defense_evasion_attempt_to_disable_syslog_service.json'; -import rule144 from './defense_evasion_file_deletion_via_shred.json'; -import rule145 from './discovery_virtual_machine_fingerprinting.json'; -import rule146 from './defense_evasion_hidden_file_dir_tmp.json'; -import rule147 from './defense_evasion_deletion_of_bash_command_line_history.json'; -import rule148 from './impact_cloudwatch_log_group_deletion.json'; -import rule149 from './impact_cloudwatch_log_stream_deletion.json'; -import rule150 from './impact_rds_instance_cluster_stoppage.json'; -import rule151 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; -import rule152 from './persistence_rds_cluster_creation.json'; -import rule153 from './credential_access_attempted_bypass_of_okta_mfa.json'; -import rule154 from './defense_evasion_waf_acl_deletion.json'; -import rule155 from './impact_attempt_to_revoke_okta_api_token.json'; -import rule156 from './impact_iam_group_deletion.json'; -import rule157 from './impact_possible_okta_dos_attack.json'; -import rule158 from './impact_rds_cluster_deletion.json'; -import rule159 from './initial_access_suspicious_activity_reported_by_okta_user.json'; -import rule160 from './okta_attempt_to_deactivate_okta_policy.json'; -import rule161 from './okta_attempt_to_deactivate_okta_policy_rule.json'; -import rule162 from './okta_attempt_to_modify_okta_network_zone.json'; -import rule163 from './okta_attempt_to_modify_okta_policy.json'; -import rule164 from './okta_attempt_to_modify_okta_policy_rule.json'; -import rule165 from './okta_threat_detected_by_okta_threatinsight.json'; -import rule166 from './persistence_administrator_privileges_assigned_to_okta_group.json'; -import rule167 from './persistence_attempt_to_create_okta_api_token.json'; -import rule168 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; -import rule169 from './defense_evasion_cloudtrail_logging_deleted.json'; -import rule170 from './defense_evasion_ec2_network_acl_deletion.json'; -import rule171 from './impact_iam_deactivate_mfa_device.json'; -import rule172 from './defense_evasion_s3_bucket_configuration_deletion.json'; -import rule173 from './defense_evasion_guardduty_detector_deletion.json'; -import rule174 from './okta_attempt_to_delete_okta_policy.json'; -import rule175 from './credential_access_iam_user_addition_to_group.json'; -import rule176 from './persistence_ec2_network_acl_creation.json'; -import rule177 from './impact_ec2_disable_ebs_encryption.json'; -import rule178 from './persistence_iam_group_creation.json'; -import rule179 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; -import rule180 from './collection_cloudtrail_logging_created.json'; -import rule181 from './defense_evasion_cloudtrail_logging_suspended.json'; -import rule182 from './impact_cloudtrail_logging_updated.json'; -import rule183 from './initial_access_console_login_root.json'; -import rule184 from './defense_evasion_cloudwatch_alarm_deletion.json'; -import rule185 from './defense_evasion_ec2_flow_log_deletion.json'; -import rule186 from './defense_evasion_configuration_recorder_stopped.json'; -import rule187 from './exfiltration_ec2_snapshot_change_activity.json'; -import rule188 from './defense_evasion_config_service_rule_deletion.json'; -import rule189 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; -import rule190 from './command_and_control_download_rar_powershell_from_internet.json'; -import rule191 from './initial_access_password_recovery.json'; -import rule192 from './command_and_control_cobalt_strike_beacon.json'; -import rule193 from './command_and_control_fin7_c2_behavior.json'; -import rule194 from './command_and_control_halfbaked_beacon.json'; -import rule195 from './credential_access_secretsmanager_getsecretvalue.json'; -import rule196 from './initial_access_via_system_manager.json'; -import rule197 from './privilege_escalation_root_login_without_mfa.json'; -import rule198 from './privilege_escalation_updateassumerolepolicy.json'; -import rule199 from './impact_hosts_file_modified.json'; -import rule200 from './elastic_endpoint.json'; -import rule201 from './external_alerts.json'; -import rule202 from './ml_cloudtrail_error_message_spike.json'; -import rule203 from './ml_cloudtrail_rare_error_code.json'; -import rule204 from './ml_cloudtrail_rare_method_by_city.json'; -import rule205 from './ml_cloudtrail_rare_method_by_country.json'; -import rule206 from './ml_cloudtrail_rare_method_by_user.json'; -import rule207 from './credential_access_aws_iam_assume_role_brute_force.json'; -import rule208 from './credential_access_okta_brute_force_or_password_spraying.json'; -import rule209 from './initial_access_unusual_dns_service_children.json'; -import rule210 from './initial_access_unusual_dns_service_file_writes.json'; -import rule211 from './lateral_movement_dns_server_overflow.json'; -import rule212 from './credential_access_root_console_failure_brute_force.json'; -import rule213 from './initial_access_unsecure_elasticsearch_node.json'; -import rule214 from './credential_access_domain_backup_dpapi_private_keys.json'; -import rule215 from './persistence_gpo_schtask_service_creation.json'; -import rule216 from './credential_access_compress_credentials_keychains.json'; -import rule217 from './credential_access_kerberosdump_kcc.json'; -import rule218 from './defense_evasion_attempt_del_quarantine_attrib.json'; -import rule219 from './execution_suspicious_psexesvc.json'; -import rule220 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; -import rule221 from './privilege_escalation_printspooler_service_suspicious_file.json'; -import rule222 from './privilege_escalation_printspooler_suspicious_spl_file.json'; -import rule223 from './defense_evasion_azure_diagnostic_settings_deletion.json'; -import rule224 from './execution_command_virtual_machine.json'; -import rule225 from './execution_via_hidden_shell_conhost.json'; -import rule226 from './impact_resource_group_deletion.json'; -import rule227 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; -import rule228 from './persistence_via_update_orchestrator_service_hijack.json'; -import rule229 from './collection_update_event_hub_auth_rule.json'; -import rule230 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; -import rule231 from './credential_access_iis_connectionstrings_dumping.json'; -import rule232 from './defense_evasion_event_hub_deletion.json'; -import rule233 from './defense_evasion_firewall_policy_deletion.json'; -import rule234 from './defense_evasion_sdelete_like_filename_rename.json'; -import rule235 from './lateral_movement_remote_ssh_login_enabled.json'; -import rule236 from './persistence_azure_automation_account_created.json'; -import rule237 from './persistence_azure_automation_runbook_created_or_modified.json'; -import rule238 from './persistence_azure_automation_webhook_created.json'; -import rule239 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; -import rule240 from './credential_access_attempts_to_brute_force_okta_user_account.json'; -import rule241 from './credential_access_storage_account_key_regenerated.json'; -import rule242 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; -import rule243 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; -import rule244 from './defense_evasion_unusual_system_vp_child_program.json'; -import rule245 from './discovery_blob_container_access_mod.json'; -import rule246 from './persistence_mfa_disabled_for_azure_user.json'; -import rule247 from './persistence_user_added_as_owner_for_azure_application.json'; -import rule248 from './persistence_user_added_as_owner_for_azure_service_principal.json'; -import rule249 from './defense_evasion_dotnet_compiler_parent_process.json'; -import rule250 from './defense_evasion_suspicious_managedcode_host_process.json'; -import rule251 from './execution_command_shell_started_by_unusual_process.json'; -import rule252 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; -import rule253 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; -import rule254 from './defense_evasion_masquerading_werfault.json'; -import rule255 from './credential_access_key_vault_modified.json'; -import rule256 from './credential_access_mimikatz_memssp_default_logs.json'; -import rule257 from './defense_evasion_code_injection_conhost.json'; -import rule258 from './defense_evasion_network_watcher_deletion.json'; -import rule259 from './initial_access_external_guest_user_invite.json'; -import rule260 from './defense_evasion_masquerading_renamed_autoit.json'; -import rule261 from './impact_azure_automation_runbook_deleted.json'; -import rule262 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; -import rule263 from './persistence_azure_conditional_access_policy_modified.json'; -import rule264 from './persistence_azure_privileged_identity_management_role_modified.json'; -import rule265 from './command_and_control_teamviewer_remote_file_copy.json'; -import rule266 from './defense_evasion_installutil_beacon.json'; -import rule267 from './defense_evasion_mshta_beacon.json'; -import rule268 from './defense_evasion_network_connection_from_windows_binary.json'; -import rule269 from './defense_evasion_rundll32_no_arguments.json'; -import rule270 from './defense_evasion_suspicious_scrobj_load.json'; -import rule271 from './defense_evasion_suspicious_wmi_script.json'; -import rule272 from './execution_ms_office_written_file.json'; -import rule273 from './execution_pdf_written_file.json'; -import rule274 from './lateral_movement_cmd_service.json'; -import rule275 from './persistence_app_compat_shim.json'; -import rule276 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; -import rule277 from './command_and_control_remote_file_copy_mpcmdrun.json'; -import rule278 from './defense_evasion_execution_suspicious_explorer_winword.json'; -import rule279 from './defense_evasion_suspicious_zoom_child_process.json'; -import rule280 from './ml_linux_anomalous_compiler_activity.json'; -import rule281 from './ml_linux_anomalous_kernel_module_arguments.json'; -import rule282 from './ml_linux_anomalous_sudo_activity.json'; -import rule283 from './ml_linux_system_information_discovery.json'; -import rule284 from './ml_linux_system_network_configuration_discovery.json'; -import rule285 from './ml_linux_system_network_connection_discovery.json'; -import rule286 from './ml_linux_system_process_discovery.json'; -import rule287 from './ml_linux_system_user_discovery.json'; -import rule288 from './discovery_post_exploitation_public_ip_reconnaissance.json'; -import rule289 from './initial_access_zoom_meeting_with_no_passcode.json'; -import rule290 from './defense_evasion_gcp_logging_sink_deletion.json'; -import rule291 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; -import rule292 from './defense_evasion_gcp_firewall_rule_created.json'; -import rule293 from './defense_evasion_gcp_firewall_rule_deleted.json'; -import rule294 from './defense_evasion_gcp_firewall_rule_modified.json'; -import rule295 from './defense_evasion_gcp_logging_bucket_deletion.json'; -import rule296 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; -import rule297 from './impact_gcp_storage_bucket_deleted.json'; -import rule298 from './initial_access_gcp_iam_custom_role_creation.json'; -import rule299 from './persistence_gcp_iam_service_account_key_deletion.json'; -import rule300 from './persistence_gcp_key_created_for_service_account.json'; -import rule301 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; -import rule302 from './exfiltration_gcp_logging_sink_modification.json'; -import rule303 from './impact_gcp_iam_role_deletion.json'; -import rule304 from './impact_gcp_service_account_deleted.json'; -import rule305 from './impact_gcp_service_account_disabled.json'; -import rule306 from './impact_gcp_virtual_private_cloud_network_deleted.json'; -import rule307 from './impact_gcp_virtual_private_cloud_route_created.json'; -import rule308 from './impact_gcp_virtual_private_cloud_route_deleted.json'; -import rule309 from './ml_linux_anomalous_metadata_process.json'; -import rule310 from './ml_linux_anomalous_metadata_user.json'; -import rule311 from './ml_windows_anomalous_metadata_process.json'; -import rule312 from './ml_windows_anomalous_metadata_user.json'; -import rule313 from './persistence_gcp_service_account_created.json'; -import rule314 from './collection_gcp_pub_sub_subscription_creation.json'; -import rule315 from './collection_gcp_pub_sub_topic_creation.json'; -import rule316 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; -import rule317 from './persistence_azure_pim_user_added_global_admin.json'; -import rule318 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; -import rule319 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; -import rule320 from './defense_evasion_execution_lolbas_wuauclt.json'; -import rule321 from './privilege_escalation_unusual_svchost_childproc_childless.json'; -import rule322 from './lateral_movement_rdp_tunnel_plink.json'; -import rule323 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; -import rule324 from './persistence_ms_office_addins_file.json'; -import rule325 from './discovery_adfind_command_activity.json'; -import rule326 from './discovery_security_software_wmic.json'; -import rule327 from './execution_command_shell_via_rundll32.json'; -import rule328 from './execution_suspicious_cmd_wmi.json'; -import rule329 from './lateral_movement_via_startup_folder_rdp_smb.json'; -import rule330 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; -import rule331 from './privilege_escalation_uac_bypass_mock_windir.json'; -import rule332 from './defense_evasion_potential_processherpaderping.json'; -import rule333 from './privilege_escalation_uac_bypass_dll_sideloading.json'; -import rule334 from './execution_shared_modules_local_sxs_dll.json'; -import rule335 from './privilege_escalation_uac_bypass_com_clipup.json'; -import rule336 from './initial_access_via_explorer_suspicious_child_parent_args.json'; -import rule337 from './execution_from_unusual_directory.json'; -import rule338 from './execution_from_unusual_path_cmdline.json'; -import rule339 from './credential_access_kerberoasting_unusual_process.json'; -import rule340 from './discovery_peripheral_device.json'; -import rule341 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; -import rule342 from './defense_evasion_deleting_websvr_access_logs.json'; -import rule343 from './defense_evasion_log_files_deleted.json'; -import rule344 from './defense_evasion_timestomp_touch.json'; -import rule345 from './lateral_movement_dcom_hta.json'; -import rule346 from './lateral_movement_execution_via_file_shares_sequence.json'; -import rule347 from './privilege_escalation_uac_bypass_com_ieinstal.json'; -import rule348 from './command_and_control_common_webservices.json'; -import rule349 from './command_and_control_encrypted_channel_freesslcert.json'; -import rule350 from './defense_evasion_process_termination_followed_by_deletion.json'; -import rule351 from './lateral_movement_remote_file_copy_hidden_share.json'; -import rule352 from './attempt_to_deactivate_okta_network_zone.json'; -import rule353 from './attempt_to_delete_okta_network_zone.json'; -import rule354 from './lateral_movement_dcom_mmc20.json'; -import rule355 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; -import rule356 from './okta_attempt_to_deactivate_okta_application.json'; -import rule357 from './okta_attempt_to_delete_okta_application.json'; -import rule358 from './okta_attempt_to_delete_okta_policy_rule.json'; -import rule359 from './okta_attempt_to_modify_okta_application.json'; -import rule360 from './persistence_administrator_role_assigned_to_okta_user.json'; -import rule361 from './lateral_movement_executable_tool_transfer_smb.json'; -import rule362 from './command_and_control_dns_tunneling_nslookup.json'; -import rule363 from './lateral_movement_execution_from_tsclient_mup.json'; -import rule364 from './lateral_movement_rdp_sharprdp_target.json'; -import rule365 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; -import rule366 from './execution_suspicious_short_program_name.json'; -import rule367 from './lateral_movement_incoming_wmi.json'; -import rule368 from './persistence_via_hidden_run_key_valuename.json'; -import rule369 from './credential_access_potential_ssh_bruteforce.json'; -import rule370 from './credential_access_promt_for_pwd_via_osascript.json'; -import rule371 from './lateral_movement_remote_services.json'; -import rule372 from './application_added_to_google_workspace_domain.json'; -import rule373 from './domain_added_to_google_workspace_trusted_domains.json'; -import rule374 from './execution_suspicious_image_load_wmi_ms_office.json'; -import rule375 from './execution_suspicious_powershell_imgload.json'; -import rule376 from './google_workspace_admin_role_deletion.json'; -import rule377 from './google_workspace_mfa_enforcement_disabled.json'; -import rule378 from './google_workspace_policy_modified.json'; -import rule379 from './mfa_disabled_for_google_workspace_organization.json'; -import rule380 from './persistence_evasion_registry_ifeo_injection.json'; -import rule381 from './persistence_google_workspace_admin_role_assigned_to_user.json'; -import rule382 from './persistence_google_workspace_custom_admin_role_created.json'; -import rule383 from './persistence_google_workspace_role_modified.json'; -import rule384 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; -import rule385 from './defense_evasion_masquerading_trusted_directory.json'; -import rule386 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; -import rule387 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; -import rule388 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; -import rule389 from './persistence_appcertdlls_registry.json'; -import rule390 from './persistence_appinitdlls_registry.json'; -import rule391 from './persistence_registry_uncommon.json'; -import rule392 from './persistence_run_key_and_startup_broad.json'; -import rule393 from './persistence_services_registry.json'; -import rule394 from './persistence_startup_folder_file_written_by_suspicious_process.json'; -import rule395 from './persistence_startup_folder_scripts.json'; -import rule396 from './persistence_suspicious_com_hijack_registry.json'; -import rule397 from './persistence_via_lsa_security_support_provider_registry.json'; -import rule398 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; -import rule399 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; -import rule400 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; -import rule401 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; -import rule402 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; -import rule403 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; -import rule404 from './lateral_movement_suspicious_rdp_client_imageload.json'; -import rule405 from './persistence_runtime_run_key_startup_susp_procs.json'; -import rule406 from './persistence_suspicious_scheduled_task_runtime.json'; -import rule407 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; -import rule408 from './lateral_movement_scheduled_task_target.json'; -import rule409 from './persistence_microsoft_365_exchange_management_role_assignment.json'; -import rule410 from './persistence_microsoft_365_teams_guest_access_enabled.json'; -import rule411 from './credential_access_dump_registry_hives.json'; -import rule412 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; -import rule413 from './persistence_ms_outlook_vba_template.json'; -import rule414 from './persistence_suspicious_service_created_registry.json'; -import rule415 from './privilege_escalation_named_pipe_impersonation.json'; -import rule416 from './credential_access_cmdline_dump_tool.json'; -import rule417 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; -import rule418 from './credential_access_lsass_memdump_file_created.json'; -import rule419 from './lateral_movement_incoming_winrm_shell_execution.json'; -import rule420 from './lateral_movement_powershell_remoting_target.json'; -import rule421 from './defense_evasion_hide_encoded_executable_registry.json'; -import rule422 from './defense_evasion_port_forwarding_added_registry.json'; -import rule423 from './lateral_movement_rdp_enabled_registry.json'; -import rule424 from './privilege_escalation_printspooler_registry_copyfiles.json'; -import rule425 from './privilege_escalation_rogue_windir_environment_var.json'; -import rule426 from './initial_access_scripts_process_started_via_wmi.json'; -import rule427 from './command_and_control_iexplore_via_com.json'; -import rule428 from './command_and_control_remote_file_copy_scripts.json'; -import rule429 from './persistence_local_scheduled_task_scripting.json'; -import rule430 from './persistence_startup_folder_file_written_by_unsigned_process.json'; -import rule431 from './command_and_control_remote_file_copy_powershell.json'; -import rule432 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; -import rule433 from './microsoft_365_teams_custom_app_interaction_allowed.json'; -import rule434 from './persistence_microsoft_365_teams_external_access_enabled.json'; -import rule435 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; -import rule436 from './defense_evasion_stop_process_service_threshold.json'; -import rule437 from './collection_winrar_encryption.json'; -import rule438 from './defense_evasion_unusual_dir_ads.json'; -import rule439 from './discovery_admin_recon.json'; -import rule440 from './discovery_file_dir_discovery.json'; -import rule441 from './discovery_net_view.json'; -import rule442 from './discovery_query_registry_via_reg.json'; -import rule443 from './discovery_remote_system_discovery_commands_windows.json'; -import rule444 from './persistence_via_windows_management_instrumentation_event_subscription.json'; -import rule445 from './execution_scripting_osascript_exec_followed_by_netcon.json'; -import rule446 from './execution_shell_execution_via_apple_scripting.json'; -import rule447 from './persistence_creation_change_launch_agents_file.json'; -import rule448 from './persistence_creation_modif_launch_deamon_sequence.json'; -import rule449 from './persistence_folder_action_scripts_runtime.json'; -import rule450 from './persistence_login_logout_hooks_defaults.json'; -import rule451 from './privilege_escalation_explicit_creds_via_apple_scripting.json'; -import rule452 from './command_and_control_sunburst_c2_activity_detected.json'; -import rule453 from './defense_evasion_azure_application_credential_modification.json'; -import rule454 from './defense_evasion_azure_service_principal_addition.json'; -import rule455 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; -import rule456 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; -import rule457 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; -import rule458 from './initial_access_azure_active_directory_powershell_signin.json'; -import rule459 from './collection_email_powershell_exchange_mailbox.json'; -import rule460 from './collection_persistence_powershell_exch_mailbox_activesync_add_device.json'; -import rule461 from './execution_scheduled_task_powershell_source.json'; +import rule1 from './credential_access_access_to_browser_credentials_procargs.json'; +import rule2 from './defense_evasion_tcc_bypass_mounted_apfs_access.json'; +import rule3 from './persistence_enable_root_account.json'; +import rule4 from './defense_evasion_unload_endpointsecurity_kext.json'; +import rule5 from './persistence_account_creation_hide_at_logon.json'; +import rule6 from './persistence_creation_hidden_login_item_osascript.json'; +import rule7 from './persistence_evasion_hidden_launch_agent_deamon_creation.json'; +import rule8 from './privilege_escalation_local_user_added_to_admin.json'; +import rule9 from './credential_access_keychain_pwd_retrieval_security_cmd.json'; +import rule10 from './credential_access_systemkey_dumping.json'; +import rule11 from './execution_defense_evasion_electron_app_childproc_node_js.json'; +import rule12 from './execution_revershell_via_shell_cmd.json'; +import rule13 from './persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json'; +import rule14 from './privilege_escalation_persistence_phantom_dll.json'; +import rule15 from './defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json'; +import rule16 from './lateral_movement_credential_access_kerberos_bifrostconsole.json'; +import rule17 from './lateral_movement_vpn_connection_attempt.json'; +import rule18 from './apm_403_response_to_a_post.json'; +import rule19 from './apm_405_response_method_not_allowed.json'; +import rule20 from './apm_null_user_agent.json'; +import rule21 from './apm_sqlmap_user_agent.json'; +import rule22 from './command_and_control_dns_directly_to_the_internet.json'; +import rule23 from './command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json'; +import rule24 from './command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; +import rule25 from './command_and_control_nat_traversal_port_activity.json'; +import rule26 from './command_and_control_port_26_activity.json'; +import rule27 from './command_and_control_port_8000_activity_to_the_internet.json'; +import rule28 from './command_and_control_pptp_point_to_point_tunneling_protocol_activity.json'; +import rule29 from './command_and_control_proxy_port_activity_to_the_internet.json'; +import rule30 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule31 from './command_and_control_smtp_to_the_internet.json'; +import rule32 from './command_and_control_sql_server_port_activity_to_the_internet.json'; +import rule33 from './command_and_control_ssh_secure_shell_from_the_internet.json'; +import rule34 from './command_and_control_ssh_secure_shell_to_the_internet.json'; +import rule35 from './command_and_control_telnet_port_activity.json'; +import rule36 from './command_and_control_tor_activity_to_the_internet.json'; +import rule37 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; +import rule38 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; +import rule39 from './credential_access_tcpdump_activity.json'; +import rule40 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; +import rule41 from './defense_evasion_clearing_windows_event_logs.json'; +import rule42 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; +import rule43 from './defense_evasion_deleting_backup_catalogs_with_wbadmin.json'; +import rule44 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; +import rule45 from './defense_evasion_encoding_or_decoding_files_via_certutil.json'; +import rule46 from './defense_evasion_execution_via_trusted_developer_utilities.json'; +import rule47 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; +import rule48 from './defense_evasion_msbuild_making_network_connections.json'; +import rule49 from './defense_evasion_unusual_network_connection_via_rundll32.json'; +import rule50 from './defense_evasion_unusual_process_network_connection.json'; +import rule51 from './defense_evasion_via_filter_manager.json'; +import rule52 from './defense_evasion_volume_shadow_copy_deletion_via_wmic.json'; +import rule53 from './discovery_process_discovery_via_tasklist_command.json'; +import rule54 from './discovery_whoami_command_activity.json'; +import rule55 from './discovery_whoami_commmand.json'; +import rule56 from './endpoint_adversary_behavior_detected.json'; +import rule57 from './endpoint_cred_dumping_detected.json'; +import rule58 from './endpoint_cred_dumping_prevented.json'; +import rule59 from './endpoint_cred_manipulation_detected.json'; +import rule60 from './endpoint_cred_manipulation_prevented.json'; +import rule61 from './endpoint_exploit_detected.json'; +import rule62 from './endpoint_exploit_prevented.json'; +import rule63 from './endpoint_malware_detected.json'; +import rule64 from './endpoint_malware_prevented.json'; +import rule65 from './endpoint_permission_theft_detected.json'; +import rule66 from './endpoint_permission_theft_prevented.json'; +import rule67 from './endpoint_process_injection_detected.json'; +import rule68 from './endpoint_process_injection_prevented.json'; +import rule69 from './endpoint_ransomware_detected.json'; +import rule70 from './endpoint_ransomware_prevented.json'; +import rule71 from './execution_command_prompt_connecting_to_the_internet.json'; +import rule72 from './execution_command_shell_started_by_powershell.json'; +import rule73 from './execution_command_shell_started_by_svchost.json'; +import rule74 from './execution_html_help_executable_program_connecting_to_the_internet.json'; +import rule75 from './execution_psexec_lateral_movement_command.json'; +import rule76 from './execution_register_server_program_connecting_to_the_internet.json'; +import rule77 from './execution_via_compiled_html_file.json'; +import rule78 from './impact_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule79 from './initial_access_rdp_remote_desktop_protocol_to_the_internet.json'; +import rule80 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; +import rule81 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; +import rule82 from './initial_access_script_executing_powershell.json'; +import rule83 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule84 from './initial_access_suspicious_ms_office_child_process.json'; +import rule85 from './initial_access_suspicious_ms_outlook_child_process.json'; +import rule86 from './lateral_movement_direct_outbound_smb_connection.json'; +import rule87 from './lateral_movement_local_service_commands.json'; +import rule88 from './linux_hping_activity.json'; +import rule89 from './linux_iodine_activity.json'; +import rule90 from './linux_mknod_activity.json'; +import rule91 from './linux_netcat_network_connection.json'; +import rule92 from './linux_nmap_activity.json'; +import rule93 from './linux_nping_activity.json'; +import rule94 from './linux_process_started_in_temp_directory.json'; +import rule95 from './linux_socat_activity.json'; +import rule96 from './linux_strace_activity.json'; +import rule97 from './persistence_adobe_hijack_persistence.json'; +import rule98 from './persistence_kernel_module_activity.json'; +import rule99 from './persistence_local_scheduled_task_commands.json'; +import rule100 from './persistence_priv_escalation_via_accessibility_features.json'; +import rule101 from './persistence_shell_activity_by_web_server.json'; +import rule102 from './persistence_system_shells_via_services.json'; +import rule103 from './persistence_user_account_creation.json'; +import rule104 from './persistence_via_application_shimming.json'; +import rule105 from './privilege_escalation_unusual_parentchild_relationship.json'; +import rule106 from './defense_evasion_modification_of_boot_config.json'; +import rule107 from './privilege_escalation_uac_bypass_event_viewer.json'; +import rule108 from './defense_evasion_msxsl_network.json'; +import rule109 from './discovery_net_command_system_account.json'; +import rule110 from './command_and_control_certutil_network_connection.json'; +import rule111 from './defense_evasion_cve_2020_0601.json'; +import rule112 from './credential_access_credential_dumping_msbuild.json'; +import rule113 from './defense_evasion_execution_msbuild_started_by_office_app.json'; +import rule114 from './defense_evasion_execution_msbuild_started_by_script.json'; +import rule115 from './defense_evasion_execution_msbuild_started_by_system_process.json'; +import rule116 from './defense_evasion_execution_msbuild_started_renamed.json'; +import rule117 from './defense_evasion_execution_msbuild_started_unusal_process.json'; +import rule118 from './defense_evasion_injection_msbuild.json'; +import rule119 from './execution_via_net_com_assemblies.json'; +import rule120 from './ml_linux_anomalous_network_activity.json'; +import rule121 from './ml_linux_anomalous_network_port_activity.json'; +import rule122 from './ml_linux_anomalous_network_service.json'; +import rule123 from './ml_linux_anomalous_network_url_activity.json'; +import rule124 from './ml_linux_anomalous_process_all_hosts.json'; +import rule125 from './ml_linux_anomalous_user_name.json'; +import rule126 from './ml_packetbeat_dns_tunneling.json'; +import rule127 from './ml_packetbeat_rare_dns_question.json'; +import rule128 from './ml_packetbeat_rare_server_domain.json'; +import rule129 from './ml_packetbeat_rare_urls.json'; +import rule130 from './ml_packetbeat_rare_user_agent.json'; +import rule131 from './ml_rare_process_by_host_linux.json'; +import rule132 from './ml_rare_process_by_host_windows.json'; +import rule133 from './ml_suspicious_login_activity.json'; +import rule134 from './ml_windows_anomalous_network_activity.json'; +import rule135 from './ml_windows_anomalous_path_activity.json'; +import rule136 from './ml_windows_anomalous_process_all_hosts.json'; +import rule137 from './ml_windows_anomalous_process_creation.json'; +import rule138 from './ml_windows_anomalous_script.json'; +import rule139 from './ml_windows_anomalous_service.json'; +import rule140 from './ml_windows_anomalous_user_name.json'; +import rule141 from './ml_windows_rare_user_runas_event.json'; +import rule142 from './ml_windows_rare_user_type10_remote_login.json'; +import rule143 from './execution_suspicious_pdf_reader.json'; +import rule144 from './privilege_escalation_sudoers_file_mod.json'; +import rule145 from './defense_evasion_iis_httplogging_disabled.json'; +import rule146 from './execution_python_tty_shell.json'; +import rule147 from './execution_perl_tty_shell.json'; +import rule148 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; +import rule149 from './defense_evasion_base64_encoding_or_decoding_activity.json'; +import rule150 from './defense_evasion_hex_encoding_or_decoding_activity.json'; +import rule151 from './defense_evasion_file_mod_writable_dir.json'; +import rule152 from './defense_evasion_disable_selinux_attempt.json'; +import rule153 from './discovery_kernel_module_enumeration.json'; +import rule154 from './lateral_movement_telnet_network_activity_external.json'; +import rule155 from './lateral_movement_telnet_network_activity_internal.json'; +import rule156 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; +import rule157 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; +import rule158 from './defense_evasion_kernel_module_removal.json'; +import rule159 from './defense_evasion_attempt_to_disable_syslog_service.json'; +import rule160 from './defense_evasion_file_deletion_via_shred.json'; +import rule161 from './discovery_virtual_machine_fingerprinting.json'; +import rule162 from './defense_evasion_hidden_file_dir_tmp.json'; +import rule163 from './defense_evasion_deletion_of_bash_command_line_history.json'; +import rule164 from './impact_cloudwatch_log_group_deletion.json'; +import rule165 from './impact_cloudwatch_log_stream_deletion.json'; +import rule166 from './impact_rds_instance_cluster_stoppage.json'; +import rule167 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; +import rule168 from './persistence_rds_cluster_creation.json'; +import rule169 from './credential_access_attempted_bypass_of_okta_mfa.json'; +import rule170 from './defense_evasion_waf_acl_deletion.json'; +import rule171 from './impact_attempt_to_revoke_okta_api_token.json'; +import rule172 from './impact_iam_group_deletion.json'; +import rule173 from './impact_possible_okta_dos_attack.json'; +import rule174 from './impact_rds_cluster_deletion.json'; +import rule175 from './initial_access_suspicious_activity_reported_by_okta_user.json'; +import rule176 from './okta_attempt_to_deactivate_okta_policy.json'; +import rule177 from './okta_attempt_to_deactivate_okta_policy_rule.json'; +import rule178 from './okta_attempt_to_modify_okta_network_zone.json'; +import rule179 from './okta_attempt_to_modify_okta_policy.json'; +import rule180 from './okta_attempt_to_modify_okta_policy_rule.json'; +import rule181 from './okta_threat_detected_by_okta_threatinsight.json'; +import rule182 from './persistence_administrator_privileges_assigned_to_okta_group.json'; +import rule183 from './persistence_attempt_to_create_okta_api_token.json'; +import rule184 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; +import rule185 from './defense_evasion_cloudtrail_logging_deleted.json'; +import rule186 from './defense_evasion_ec2_network_acl_deletion.json'; +import rule187 from './impact_iam_deactivate_mfa_device.json'; +import rule188 from './defense_evasion_s3_bucket_configuration_deletion.json'; +import rule189 from './defense_evasion_guardduty_detector_deletion.json'; +import rule190 from './okta_attempt_to_delete_okta_policy.json'; +import rule191 from './credential_access_iam_user_addition_to_group.json'; +import rule192 from './persistence_ec2_network_acl_creation.json'; +import rule193 from './impact_ec2_disable_ebs_encryption.json'; +import rule194 from './persistence_iam_group_creation.json'; +import rule195 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; +import rule196 from './collection_cloudtrail_logging_created.json'; +import rule197 from './defense_evasion_cloudtrail_logging_suspended.json'; +import rule198 from './impact_cloudtrail_logging_updated.json'; +import rule199 from './initial_access_console_login_root.json'; +import rule200 from './defense_evasion_cloudwatch_alarm_deletion.json'; +import rule201 from './defense_evasion_ec2_flow_log_deletion.json'; +import rule202 from './defense_evasion_configuration_recorder_stopped.json'; +import rule203 from './exfiltration_ec2_snapshot_change_activity.json'; +import rule204 from './defense_evasion_config_service_rule_deletion.json'; +import rule205 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; +import rule206 from './command_and_control_download_rar_powershell_from_internet.json'; +import rule207 from './initial_access_password_recovery.json'; +import rule208 from './command_and_control_cobalt_strike_beacon.json'; +import rule209 from './command_and_control_fin7_c2_behavior.json'; +import rule210 from './command_and_control_halfbaked_beacon.json'; +import rule211 from './credential_access_secretsmanager_getsecretvalue.json'; +import rule212 from './initial_access_via_system_manager.json'; +import rule213 from './privilege_escalation_root_login_without_mfa.json'; +import rule214 from './privilege_escalation_updateassumerolepolicy.json'; +import rule215 from './impact_hosts_file_modified.json'; +import rule216 from './elastic_endpoint.json'; +import rule217 from './external_alerts.json'; +import rule218 from './initial_access_login_failures.json'; +import rule219 from './initial_access_login_location.json'; +import rule220 from './initial_access_login_sessions.json'; +import rule221 from './initial_access_login_time.json'; +import rule222 from './ml_cloudtrail_error_message_spike.json'; +import rule223 from './ml_cloudtrail_rare_error_code.json'; +import rule224 from './ml_cloudtrail_rare_method_by_city.json'; +import rule225 from './ml_cloudtrail_rare_method_by_country.json'; +import rule226 from './ml_cloudtrail_rare_method_by_user.json'; +import rule227 from './credential_access_aws_iam_assume_role_brute_force.json'; +import rule228 from './credential_access_okta_brute_force_or_password_spraying.json'; +import rule229 from './initial_access_unusual_dns_service_children.json'; +import rule230 from './initial_access_unusual_dns_service_file_writes.json'; +import rule231 from './lateral_movement_dns_server_overflow.json'; +import rule232 from './credential_access_root_console_failure_brute_force.json'; +import rule233 from './initial_access_unsecure_elasticsearch_node.json'; +import rule234 from './credential_access_domain_backup_dpapi_private_keys.json'; +import rule235 from './persistence_gpo_schtask_service_creation.json'; +import rule236 from './credential_access_credentials_keychains.json'; +import rule237 from './credential_access_kerberosdump_kcc.json'; +import rule238 from './defense_evasion_attempt_del_quarantine_attrib.json'; +import rule239 from './execution_suspicious_psexesvc.json'; +import rule240 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; +import rule241 from './privilege_escalation_printspooler_service_suspicious_file.json'; +import rule242 from './privilege_escalation_printspooler_suspicious_spl_file.json'; +import rule243 from './defense_evasion_azure_diagnostic_settings_deletion.json'; +import rule244 from './execution_command_virtual_machine.json'; +import rule245 from './execution_via_hidden_shell_conhost.json'; +import rule246 from './impact_resource_group_deletion.json'; +import rule247 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; +import rule248 from './persistence_via_update_orchestrator_service_hijack.json'; +import rule249 from './collection_update_event_hub_auth_rule.json'; +import rule250 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; +import rule251 from './credential_access_iis_connectionstrings_dumping.json'; +import rule252 from './defense_evasion_event_hub_deletion.json'; +import rule253 from './defense_evasion_firewall_policy_deletion.json'; +import rule254 from './defense_evasion_sdelete_like_filename_rename.json'; +import rule255 from './lateral_movement_remote_ssh_login_enabled.json'; +import rule256 from './persistence_azure_automation_account_created.json'; +import rule257 from './persistence_azure_automation_runbook_created_or_modified.json'; +import rule258 from './persistence_azure_automation_webhook_created.json'; +import rule259 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; +import rule260 from './credential_access_attempts_to_brute_force_okta_user_account.json'; +import rule261 from './credential_access_storage_account_key_regenerated.json'; +import rule262 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; +import rule263 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; +import rule264 from './defense_evasion_unusual_system_vp_child_program.json'; +import rule265 from './discovery_blob_container_access_mod.json'; +import rule266 from './persistence_mfa_disabled_for_azure_user.json'; +import rule267 from './persistence_user_added_as_owner_for_azure_application.json'; +import rule268 from './persistence_user_added_as_owner_for_azure_service_principal.json'; +import rule269 from './defense_evasion_dotnet_compiler_parent_process.json'; +import rule270 from './defense_evasion_suspicious_managedcode_host_process.json'; +import rule271 from './execution_command_shell_started_by_unusual_process.json'; +import rule272 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; +import rule273 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; +import rule274 from './defense_evasion_masquerading_werfault.json'; +import rule275 from './credential_access_key_vault_modified.json'; +import rule276 from './credential_access_mimikatz_memssp_default_logs.json'; +import rule277 from './defense_evasion_code_injection_conhost.json'; +import rule278 from './defense_evasion_network_watcher_deletion.json'; +import rule279 from './initial_access_external_guest_user_invite.json'; +import rule280 from './defense_evasion_masquerading_renamed_autoit.json'; +import rule281 from './impact_azure_automation_runbook_deleted.json'; +import rule282 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; +import rule283 from './persistence_azure_conditional_access_policy_modified.json'; +import rule284 from './persistence_azure_privileged_identity_management_role_modified.json'; +import rule285 from './command_and_control_teamviewer_remote_file_copy.json'; +import rule286 from './defense_evasion_installutil_beacon.json'; +import rule287 from './defense_evasion_mshta_beacon.json'; +import rule288 from './defense_evasion_network_connection_from_windows_binary.json'; +import rule289 from './defense_evasion_rundll32_no_arguments.json'; +import rule290 from './defense_evasion_suspicious_scrobj_load.json'; +import rule291 from './defense_evasion_suspicious_wmi_script.json'; +import rule292 from './execution_ms_office_written_file.json'; +import rule293 from './execution_pdf_written_file.json'; +import rule294 from './lateral_movement_cmd_service.json'; +import rule295 from './persistence_app_compat_shim.json'; +import rule296 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; +import rule297 from './command_and_control_remote_file_copy_mpcmdrun.json'; +import rule298 from './defense_evasion_execution_suspicious_explorer_winword.json'; +import rule299 from './defense_evasion_suspicious_zoom_child_process.json'; +import rule300 from './ml_linux_anomalous_compiler_activity.json'; +import rule301 from './ml_linux_anomalous_kernel_module_arguments.json'; +import rule302 from './ml_linux_anomalous_sudo_activity.json'; +import rule303 from './ml_linux_system_information_discovery.json'; +import rule304 from './ml_linux_system_network_configuration_discovery.json'; +import rule305 from './ml_linux_system_network_connection_discovery.json'; +import rule306 from './ml_linux_system_process_discovery.json'; +import rule307 from './ml_linux_system_user_discovery.json'; +import rule308 from './discovery_post_exploitation_public_ip_reconnaissance.json'; +import rule309 from './initial_access_zoom_meeting_with_no_passcode.json'; +import rule310 from './defense_evasion_gcp_logging_sink_deletion.json'; +import rule311 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; +import rule312 from './defense_evasion_gcp_firewall_rule_created.json'; +import rule313 from './defense_evasion_gcp_firewall_rule_deleted.json'; +import rule314 from './defense_evasion_gcp_firewall_rule_modified.json'; +import rule315 from './defense_evasion_gcp_logging_bucket_deletion.json'; +import rule316 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; +import rule317 from './impact_gcp_storage_bucket_deleted.json'; +import rule318 from './initial_access_gcp_iam_custom_role_creation.json'; +import rule319 from './persistence_gcp_iam_service_account_key_deletion.json'; +import rule320 from './persistence_gcp_key_created_for_service_account.json'; +import rule321 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; +import rule322 from './exfiltration_gcp_logging_sink_modification.json'; +import rule323 from './impact_gcp_iam_role_deletion.json'; +import rule324 from './impact_gcp_service_account_deleted.json'; +import rule325 from './impact_gcp_service_account_disabled.json'; +import rule326 from './impact_gcp_virtual_private_cloud_network_deleted.json'; +import rule327 from './impact_gcp_virtual_private_cloud_route_created.json'; +import rule328 from './impact_gcp_virtual_private_cloud_route_deleted.json'; +import rule329 from './ml_linux_anomalous_metadata_process.json'; +import rule330 from './ml_linux_anomalous_metadata_user.json'; +import rule331 from './ml_windows_anomalous_metadata_process.json'; +import rule332 from './ml_windows_anomalous_metadata_user.json'; +import rule333 from './persistence_gcp_service_account_created.json'; +import rule334 from './collection_gcp_pub_sub_subscription_creation.json'; +import rule335 from './collection_gcp_pub_sub_topic_creation.json'; +import rule336 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; +import rule337 from './persistence_azure_pim_user_added_global_admin.json'; +import rule338 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; +import rule339 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; +import rule340 from './defense_evasion_execution_lolbas_wuauclt.json'; +import rule341 from './privilege_escalation_unusual_svchost_childproc_childless.json'; +import rule342 from './lateral_movement_rdp_tunnel_plink.json'; +import rule343 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; +import rule344 from './persistence_ms_office_addins_file.json'; +import rule345 from './discovery_adfind_command_activity.json'; +import rule346 from './discovery_security_software_wmic.json'; +import rule347 from './execution_command_shell_via_rundll32.json'; +import rule348 from './execution_suspicious_cmd_wmi.json'; +import rule349 from './lateral_movement_via_startup_folder_rdp_smb.json'; +import rule350 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; +import rule351 from './privilege_escalation_uac_bypass_mock_windir.json'; +import rule352 from './defense_evasion_potential_processherpaderping.json'; +import rule353 from './privilege_escalation_uac_bypass_dll_sideloading.json'; +import rule354 from './execution_shared_modules_local_sxs_dll.json'; +import rule355 from './privilege_escalation_uac_bypass_com_clipup.json'; +import rule356 from './initial_access_via_explorer_suspicious_child_parent_args.json'; +import rule357 from './execution_from_unusual_directory.json'; +import rule358 from './execution_from_unusual_path_cmdline.json'; +import rule359 from './credential_access_kerberoasting_unusual_process.json'; +import rule360 from './discovery_peripheral_device.json'; +import rule361 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; +import rule362 from './defense_evasion_deleting_websvr_access_logs.json'; +import rule363 from './defense_evasion_log_files_deleted.json'; +import rule364 from './defense_evasion_timestomp_touch.json'; +import rule365 from './lateral_movement_dcom_hta.json'; +import rule366 from './lateral_movement_execution_via_file_shares_sequence.json'; +import rule367 from './privilege_escalation_uac_bypass_com_ieinstal.json'; +import rule368 from './command_and_control_common_webservices.json'; +import rule369 from './command_and_control_encrypted_channel_freesslcert.json'; +import rule370 from './defense_evasion_process_termination_followed_by_deletion.json'; +import rule371 from './lateral_movement_remote_file_copy_hidden_share.json'; +import rule372 from './attempt_to_deactivate_okta_network_zone.json'; +import rule373 from './attempt_to_delete_okta_network_zone.json'; +import rule374 from './lateral_movement_dcom_mmc20.json'; +import rule375 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; +import rule376 from './okta_attempt_to_deactivate_okta_application.json'; +import rule377 from './okta_attempt_to_delete_okta_application.json'; +import rule378 from './okta_attempt_to_delete_okta_policy_rule.json'; +import rule379 from './okta_attempt_to_modify_okta_application.json'; +import rule380 from './persistence_administrator_role_assigned_to_okta_user.json'; +import rule381 from './lateral_movement_executable_tool_transfer_smb.json'; +import rule382 from './command_and_control_dns_tunneling_nslookup.json'; +import rule383 from './lateral_movement_execution_from_tsclient_mup.json'; +import rule384 from './lateral_movement_rdp_sharprdp_target.json'; +import rule385 from './defense_evasion_clearing_windows_security_logs.json'; +import rule386 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; +import rule387 from './execution_suspicious_short_program_name.json'; +import rule388 from './lateral_movement_incoming_wmi.json'; +import rule389 from './persistence_via_hidden_run_key_valuename.json'; +import rule390 from './credential_access_potential_ssh_bruteforce.json'; +import rule391 from './credential_access_promt_for_pwd_via_osascript.json'; +import rule392 from './lateral_movement_remote_services.json'; +import rule393 from './application_added_to_google_workspace_domain.json'; +import rule394 from './domain_added_to_google_workspace_trusted_domains.json'; +import rule395 from './execution_suspicious_image_load_wmi_ms_office.json'; +import rule396 from './execution_suspicious_powershell_imgload.json'; +import rule397 from './google_workspace_admin_role_deletion.json'; +import rule398 from './google_workspace_mfa_enforcement_disabled.json'; +import rule399 from './google_workspace_policy_modified.json'; +import rule400 from './mfa_disabled_for_google_workspace_organization.json'; +import rule401 from './persistence_evasion_registry_ifeo_injection.json'; +import rule402 from './persistence_google_workspace_admin_role_assigned_to_user.json'; +import rule403 from './persistence_google_workspace_custom_admin_role_created.json'; +import rule404 from './persistence_google_workspace_role_modified.json'; +import rule405 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; +import rule406 from './defense_evasion_masquerading_trusted_directory.json'; +import rule407 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; +import rule408 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; +import rule409 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; +import rule410 from './persistence_appcertdlls_registry.json'; +import rule411 from './persistence_appinitdlls_registry.json'; +import rule412 from './persistence_registry_uncommon.json'; +import rule413 from './persistence_run_key_and_startup_broad.json'; +import rule414 from './persistence_services_registry.json'; +import rule415 from './persistence_startup_folder_file_written_by_suspicious_process.json'; +import rule416 from './persistence_startup_folder_scripts.json'; +import rule417 from './persistence_suspicious_com_hijack_registry.json'; +import rule418 from './persistence_via_lsa_security_support_provider_registry.json'; +import rule419 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; +import rule420 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; +import rule421 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; +import rule422 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; +import rule423 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; +import rule424 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; +import rule425 from './lateral_movement_suspicious_rdp_client_imageload.json'; +import rule426 from './persistence_runtime_run_key_startup_susp_procs.json'; +import rule427 from './persistence_suspicious_scheduled_task_runtime.json'; +import rule428 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; +import rule429 from './lateral_movement_scheduled_task_target.json'; +import rule430 from './persistence_microsoft_365_exchange_management_role_assignment.json'; +import rule431 from './persistence_microsoft_365_teams_guest_access_enabled.json'; +import rule432 from './credential_access_dump_registry_hives.json'; +import rule433 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; +import rule434 from './persistence_ms_outlook_vba_template.json'; +import rule435 from './persistence_suspicious_service_created_registry.json'; +import rule436 from './privilege_escalation_named_pipe_impersonation.json'; +import rule437 from './credential_access_cmdline_dump_tool.json'; +import rule438 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; +import rule439 from './credential_access_lsass_memdump_file_created.json'; +import rule440 from './lateral_movement_incoming_winrm_shell_execution.json'; +import rule441 from './lateral_movement_powershell_remoting_target.json'; +import rule442 from './defense_evasion_hide_encoded_executable_registry.json'; +import rule443 from './defense_evasion_port_forwarding_added_registry.json'; +import rule444 from './lateral_movement_rdp_enabled_registry.json'; +import rule445 from './privilege_escalation_printspooler_registry_copyfiles.json'; +import rule446 from './privilege_escalation_rogue_windir_environment_var.json'; +import rule447 from './initial_access_scripts_process_started_via_wmi.json'; +import rule448 from './command_and_control_iexplore_via_com.json'; +import rule449 from './command_and_control_remote_file_copy_scripts.json'; +import rule450 from './persistence_local_scheduled_task_scripting.json'; +import rule451 from './persistence_startup_folder_file_written_by_unsigned_process.json'; +import rule452 from './command_and_control_remote_file_copy_powershell.json'; +import rule453 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; +import rule454 from './microsoft_365_teams_custom_app_interaction_allowed.json'; +import rule455 from './persistence_microsoft_365_teams_external_access_enabled.json'; +import rule456 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; +import rule457 from './defense_evasion_stop_process_service_threshold.json'; +import rule458 from './collection_winrar_encryption.json'; +import rule459 from './defense_evasion_unusual_dir_ads.json'; +import rule460 from './discovery_admin_recon.json'; +import rule461 from './discovery_file_dir_discovery.json'; +import rule462 from './discovery_net_view.json'; +import rule463 from './discovery_query_registry_via_reg.json'; +import rule464 from './discovery_remote_system_discovery_commands_windows.json'; +import rule465 from './persistence_via_windows_management_instrumentation_event_subscription.json'; +import rule466 from './execution_scripting_osascript_exec_followed_by_netcon.json'; +import rule467 from './execution_shell_execution_via_apple_scripting.json'; +import rule468 from './persistence_creation_change_launch_agents_file.json'; +import rule469 from './persistence_creation_modif_launch_deamon_sequence.json'; +import rule470 from './persistence_folder_action_scripts_runtime.json'; +import rule471 from './persistence_login_logout_hooks_defaults.json'; +import rule472 from './privilege_escalation_explicit_creds_via_scripting.json'; +import rule473 from './command_and_control_sunburst_c2_activity_detected.json'; +import rule474 from './defense_evasion_azure_application_credential_modification.json'; +import rule475 from './defense_evasion_azure_service_principal_addition.json'; +import rule476 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; +import rule477 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; +import rule478 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; +import rule479 from './initial_access_azure_active_directory_powershell_signin.json'; +import rule480 from './collection_email_powershell_exchange_mailbox.json'; +import rule481 from './collection_persistence_powershell_exch_mailbox_activesync_add_device.json'; +import rule482 from './execution_scheduled_task_powershell_source.json'; +import rule483 from './persistence_docker_shortcuts_plist_modification.json'; +import rule484 from './persistence_evasion_hidden_local_account_creation.json'; +import rule485 from './persistence_finder_sync_plugin_pluginkit.json'; +import rule486 from './discovery_security_software_grep.json'; +import rule487 from './credential_access_cookies_chromium_browsers_debugging.json'; +import rule488 from './credential_access_ssh_backdoor_log.json'; +import rule489 from './persistence_credential_access_modify_auth_module_or_config.json'; +import rule490 from './persistence_credential_access_modify_ssh_binaries.json'; +import rule491 from './credential_access_collection_sensitive_files.json'; +import rule492 from './persistence_ssh_authorized_keys_modification.json'; +import rule493 from './defense_evasion_defender_disabled_via_registry.json'; +import rule494 from './defense_evasion_privacy_controls_tcc_database_modification.json'; +import rule495 from './execution_initial_access_suspicious_browser_childproc.json'; +import rule496 from './execution_script_via_automator_workflows.json'; +import rule497 from './persistence_modification_sublime_app_plugin_or_script.json'; +import rule498 from './privilege_escalation_applescript_with_admin_privs.json'; +import rule499 from './credential_access_dumping_keychain_security.json'; +import rule500 from './initial_access_azure_active_directory_high_risk_signin.json'; +import rule501 from './initial_access_suspicious_mac_ms_office_child_process.json'; +import rule502 from './credential_access_mitm_localhost_webproxy.json'; +import rule503 from './persistence_kde_autostart_modification.json'; +import rule504 from './persistence_user_account_added_to_privileged_group_ad.json'; +import rule505 from './defense_evasion_attempt_to_disable_gatekeeper.json'; +import rule506 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; +import rule507 from './persistence_emond_rules_file_creation.json'; +import rule508 from './persistence_emond_rules_process_execution.json'; +import rule509 from './discovery_users_domain_built_in_commands.json'; +import rule510 from './execution_pentest_eggshell_remote_admin_tool.json'; +import rule511 from './defense_evasion_install_root_certificate.json'; +import rule512 from './persistence_credential_access_authorization_plugin_creation.json'; +import rule513 from './persistence_directory_services_plugins_modification.json'; +import rule514 from './defense_evasion_modify_environment_launchctl.json'; +import rule515 from './defense_evasion_safari_config_change.json'; +import rule516 from './defense_evasion_apple_softupdates_modification.json'; +import rule517 from './persistence_cron_jobs_creation_and_runtime.json'; +import rule518 from './credential_access_mod_wdigest_security_provider.json'; +import rule519 from './credential_access_saved_creds_vaultcmd.json'; +import rule520 from './defense_evasion_file_creation_mult_extension.json'; +import rule521 from './execution_enumeration_via_wmiprvse.json'; +import rule522 from './execution_suspicious_jar_child_process.json'; +import rule523 from './persistence_shell_profile_modification.json'; +import rule524 from './persistence_suspicious_calendar_modification.json'; +import rule525 from './persistence_time_provider_mod.json'; +import rule526 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; +import rule527 from './defense_evasion_sip_provider_mod.json'; +import rule528 from './execution_com_object_xwizard.json'; +import rule529 from './privilege_escalation_disable_uac_registry.json'; +import rule530 from './defense_evasion_unusual_ads_file_creation.json'; +import rule531 from './persistence_loginwindow_plist_modification.json'; +import rule532 from './persistence_periodic_tasks_file_mdofiy.json'; +import rule533 from './persistence_via_atom_init_file_modification.json'; +import rule534 from './privilege_escalation_lsa_auth_package.json'; +import rule535 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; +import rule536 from './credential_access_dumping_hashes_bi_cmds.json'; +import rule537 from './lateral_movement_mounting_smb_share.json'; +import rule538 from './privilege_escalation_echo_nopasswd_sudoers.json'; +import rule539 from './privilege_escalation_ld_preload_shared_object_modif.json'; +import rule540 from './privilege_escalation_root_crontab_filemod.json'; +import rule541 from './defense_evasion_create_mod_root_certificate.json'; +import rule542 from './privilege_escalation_sudo_buffer_overflow.json'; export const rawRules = [ rule1, @@ -934,4 +1015,85 @@ export const rawRules = [ rule459, rule460, rule461, + rule462, + rule463, + rule464, + rule465, + rule466, + rule467, + rule468, + rule469, + rule470, + rule471, + rule472, + rule473, + rule474, + rule475, + rule476, + rule477, + rule478, + rule479, + rule480, + rule481, + rule482, + rule483, + rule484, + rule485, + rule486, + rule487, + rule488, + rule489, + rule490, + rule491, + rule492, + rule493, + rule494, + rule495, + rule496, + rule497, + rule498, + rule499, + rule500, + rule501, + rule502, + rule503, + rule504, + rule505, + rule506, + rule507, + rule508, + rule509, + rule510, + rule511, + rule512, + rule513, + rule514, + rule515, + rule516, + rule517, + rule518, + rule519, + rule520, + rule521, + rule522, + rule523, + rule524, + rule525, + rule526, + rule527, + rule528, + rule529, + rule530, + rule531, + rule532, + rule533, + rule534, + rule535, + rule536, + rule537, + rule538, + rule539, + rule540, + rule541, + rule542, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json new file mode 100644 index 00000000000000..230c5e33b75610 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic", + "Willem D'Haese" + ], + "description": "Identifies high risk Azure Active Directory (AD) sign-ins by leveraging Microsoft's Identity Protection machine learning and heuristics. Identity Protection categorizes risk into three tiers: low, medium, and high. While Microsoft does not provide specific details about how risk is calculated, each level brings higher confidence that the user or sign-in is compromised.", + "from": "now-25m", + "index": [ + "filebeat-*", + "logs-azure.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Azure Active Directory High Risk Sign-in", + "note": "The Azure Fleet Integration or Filebeat module must be enabled to use this rule.", + "query": "event.dataset:azure.signinlogs and azure.signinlogs.properties.risk_level_during_signin:high and event.outcome:(success or Success)", + "references": [ + "https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-risk", + "https://docs.microsoft.com/en-us/azure/active-directory/identity-protection/overview-identity-protection", + "https://docs.microsoft.com/en-us/azure/active-directory/identity-protection/howto-identity-protection-investigate-risk" + ], + "risk_score": 73, + "rule_id": "37994bca-0611-4500-ab67-5588afe73b77", + "severity": "high", + "tags": [ + "Elastic", + "Cloud", + "Azure", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json new file mode 100644 index 00000000000000..e825c1b14e30cc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that the maximum number of failed login attempts has been reached for a user.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Max Failed Login Attempts", + "query": "event.module:auditd and event.action:\"failed-log-in-too-many-times-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/0adbaeb273da1d45213134aa271e95987103281c/modules/pam_faillock/pam_faillock.c#L574" + ], + "risk_score": 47, + "rule_id": "fb9937ce-7e21-46bf-831d-1ad96eac674d", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json new file mode 100644 index 00000000000000..81599a9c524a09 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that a login attempt has happened from a forbidden location.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Login from Forbidden Location", + "query": "event.module:auditd and event.action:\"attempted-log-in-from-unusual-place-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_access/pam_access.c#L412" + ], + "risk_score": 73, + "rule_id": "cab4f01c-793f-4a54-a03e-e5d85b96d7af", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json new file mode 100644 index 00000000000000..2c885590925159 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that the maximum number login sessions has been reached for a user.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Max Login Sessions", + "query": "event.module:auditd and event.action:\"opened-too-many-sessions-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/70c32cc6fca51338f92afa58eb75b1107a5c2430/modules/pam_limits/pam_limits.c#L1007" + ], + "risk_score": 47, + "rule_id": "20dc4620-3b68-4269-8124-ca5091e00ea8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json new file mode 100644 index 00000000000000..ca343e7ef9cc09 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that a login attempt occurred at a forbidden time.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Login Attempt at Forbidden Time", + "query": "event.module:auditd and event.action:\"attempted-log-in-during-unusual-hour-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_time/pam_time.c#L666" + ], + "risk_score": 47, + "rule_id": "90e28af7-1d96-4582-bf11-9a1eff21d0e5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json index 05ab69a05f7dcd..024541d71b6d36 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "RDP connections may be made directly to Internet destinations in order to access Windows cloud server instances but such connections are usually made only by engineers. In such cases, only RDP gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -59,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json index 36db52a656d289..7119e5a587c69a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of RPC traffic from the Internet. RPC is commonly used by system administrators to remotely control a system for maintenance or to use shared resources. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json index 8ba074438cf50a..6da4f9f55071f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of RPC traffic to the Internet. RPC is commonly used by system administrators to remotely control a system for maintenance or to use shared resources. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json index 543aa2bcf6da13..7caa99711275b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json index 8376bb7e62bd8b..1e7160eefdaba8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Windows Script Interpreter Executing Process via WMI", - "query": "sequence by host.id with maxspan=5s\n [library where file.name : \"wmiutils.dll\" and process.name : (\"wscript.exe\", \"cscript.exe\")]\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"wmiprvse.exe\" and\n user.domain != \"NT AUTHORITY\" and\n (process.pe.original_file_name in\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) or\n process.executable : (\"C:\\\\Users\\\\*.exe\", \"C:\\\\ProgramData\\\\*.exe\")\n )\n ]\n", + "query": "sequence by host.id with maxspan = 5s\n [library where dll.name : \"wmiutils.dll\" and process.name : (\"wscript.exe\", \"cscript.exe\")]\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"wmiprvse.exe\" and\n user.domain != \"NT AUTHORITY\" and\n (process.pe.original_file_name :\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) or\n process.executable : (\"C:\\\\Users\\\\*.exe\", \"C:\\\\ProgramData\\\\*.exe\")\n )\n ]\n", "risk_score": 47, "rule_id": "b64b183e-1a76-422d-9179-7b389513e74d", "severity": "medium", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json index aefcfa4e692715..4b480cf9b054f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of Windows file sharing (also called SMB or CIFS) traffic to the Internet. SMB is commonly used within networks to share files, printers, and other system resources amongst trusted systems. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector or for data exfiltration.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -56,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json new file mode 100644 index 00000000000000..638db6727a7265 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, and Excel). These child processes are often launched during exploitation of Office applications or by documents with malicious macros.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious macOS MS Office Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name:(\"Microsoft Word\", \"Microsoft PowerPoint\", \"Microsoft Excel\") and\n process.name:\n (\n \"bash\", \n \"dash\", \n \"sh\", \n \"tcsh\", \n \"csh\", \n \"zsh\", \n \"ksh\", \n \"fish\", \n \"python*\", \n \"perl*\", \n \"php*\", \n \"osascript\",\n \"pwsh\", \n \"curl\", \n \"wget\", \n \"cp\", \n \"mv\", \n \"base64\", \n \"launchctl\"\n )\n", + "references": [ + "https://blog.malwarebytes.com/cybercrime/2017/02/microsoft-office-macro-malware-targets-macs/" + ], + "risk_score": 47, + "rule_id": "66da12b1-ac83-40eb-814c-07ed1d82b7b9", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1566", + "name": "Phishing", + "reference": "https://attack.mitre.org/techniques/T1566/", + "subtechnique": [ + { + "id": "T1566.001", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1566/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json index 91dcfd13664093..1749be1142255f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json index 533996d75dcd4c..03c145779f3d51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json index 6e5f0cb13417c2..4dad8ac892806c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json @@ -6,9 +6,11 @@ "false_positives": [ "Werfault.exe will legitimately spawn when dns.exe crashes, but the DNS service is very stable and so this is a low occurring event. Denial of Service (DoS) attempts by intentionally crashing the service will also cause werfault.exe to spawn." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -49,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json index 7f73196493e48f..f8b58c5affe9e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies an unexpected file being modified by dns.exe, the process responsible for Windows DNS Server services, which may indicate activity related to remote code execution or other forms of exploitation.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json index 8ef22da2319c9d..d88b8b06d09aab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Explorer Child Process", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\", \"rundll32.exe\", \"cmd.exe\", \"mshta.exe\", \"regsvr32.exe\") and\n /* Explorer started via DCOM */\n process.parent.name : \"explorer.exe\" and process.parent.args : \"-Embedding\"\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "9a5b4e31-6cde-4295-9ff7-6be1b8567e1b", "severity": "medium", "tags": [ @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json index db35602753ab02..7e6aece1c359f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Service Command Lateral Movement", - "query": "sequence by process.entity_id with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n (process.name == \"sc.exe\" /* or process.pe.original_file_name == \"sc.exe\" */ ) and\n /* case insensitive */\n wildcard(process.args, \"\\\\\\\\*\") and wildcard(process.args, \"binPath=*\", \"binpath=*\") and\n (process.args : \"create\" or\n process.args : \"config\" or\n process.args : \"failure\" or\n process.args : \"start\")]\n [network where process.name : \"sc.exe\" and destination.ip != \"127.0.0.1\"]\n", + "query": "sequence by process.entity_id with maxspan = 1m\n [process where event.type in (\"start\", \"process_started\") and\n (process.name : \"sc.exe\" or process.pe.original_file_name : \"sc.exe\") and\n process.args : \"\\\\\\\\*\" and process.args : (\"binPath=*\", \"binpath=*\") and\n process.args : (\"create\", \"config\", \"failure\", \"start\")]\n [network where process.name : \"sc.exe\" and destination.ip != \"127.0.0.1\"]\n", "risk_score": 21, "rule_id": "d61cbcf8-1bc1-4cff-85ba-e7b21c5beedc", "severity": "low", @@ -84,5 +85,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json new file mode 100644 index 00000000000000..467ebb045190bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json @@ -0,0 +1,71 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies use of Bifrost, a known macOS Kerberos pentesting tool, which can be used to dump cached Kerberos tickets or attempt unauthorized authentication techniques such as pass-the-ticket/hash and kerberoasting.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Kerberos Attack via Bifrost", + "query": "event.category:process and event.type:start and process.args:(\"-action\" and (\"-kerberoast\" or askhash or asktgs or asktgt or s4u or (\"-ticket\" and ptt) or (dump and (tickets or keytab))))", + "references": [ + "https://github.com/its-a-feature/bifrost" + ], + "risk_score": 73, + "rule_id": "16904215-2c95-4ac8-bf5c-12354e047192", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1550", + "name": "Use Alternate Authentication Material", + "reference": "https://attack.mitre.org/techniques/T1550/", + "subtechnique": [ + { + "id": "T1550.003", + "name": "Pass the Ticket", + "reference": "https://attack.mitre.org/techniques/T1550/003/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json index 590f82e31b36b1..16069d546ba6ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json index 5dee8493d57ad8..7aa04a808ca998 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json index 4e04dc1d458cb3..d8c4dfb5d2a73f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json index bb461cc8321e11..6e68d545d6672f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json index bc3f48904c43f5..6d82b129a3a707 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json index f3ce3a677bd295..b3eee1df24f772 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json index be4ad485fdbe48..81a9fc221a4a77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +51,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json index 0c9453940b3bc9..56f465b8b3b31e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json index e08c758f6f693e..9c7f4948a0cd11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -49,5 +50,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json index 3a04f62733a30b..21569be362998e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json index 0eade2646dcd8b..13d51e51636ede 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json new file mode 100644 index 00000000000000..702d87b105f659 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands to mount a Server Message Block (SMB) network share. Adversaries may use valid accounts to interact with a remote network share using SMB.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Attempt to Mount SMB Share via Command Line", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n process.name : \"mount_smbfs\" or\n (process.name : \"open\" and process.args : \"smb://*\") or\n (process.name : \"mount\" and process.args : \"smbfs\") or\n (process.name : \"osascript\" and process.command_line : \"osascript*mount volume*smb://*\")\n )\n", + "references": [ + "https://www.freebsd.org/cgi/man.cgi?mount_smbfs", + "https://ss64.com/osx/mount.html" + ], + "risk_score": 21, + "rule_id": "661545b4-1a90-4f45-85ce-2ebd7c6a15d0", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.002", + "name": "SMB/Windows Admin Shares", + "reference": "https://attack.mitre.org/techniques/T1021/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json index aae62e66ad2557..b384ed97c146dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -18,7 +19,7 @@ "references": [ "https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.1" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "2772264c-6fb9-4d9d-9014-b416eed21254", "severity": "medium", "tags": [ @@ -46,5 +47,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json index 8c51622b0ab52c..f823fdb9908126 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "RDP Enabled via Registry", "query": "registry where\nregistry.path : \"HKLM\\\\SYSTEM\\\\ControlSet*\\\\Control\\\\Terminal Server\\\\fDenyTSConnections\" and\nregistry.data.strings == \"0\" and not (process.name : \"svchost.exe\" and user.domain == \"NT AUTHORITY\") and\nnot process.executable : \"C:\\\\Windows\\\\System32\\\\SystemPropertiesRemote.exe\"\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "58aa72ca-d968-4f34-b9f7-bea51d75eb50", "severity": "medium", "tags": [ @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json index 9a9a9d0e7a202d..3927c20bd54180 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json index 9f1ca61b4c62fe..b103b9962aec3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Remote Desktop Tunneling Detected", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and \n/* RDP port and usual SSH tunneling related switches in commandline */\nwildcard(process.args, \"*:3389\") and wildcard(process.args,\"-L\", \"-P\", \"-R\", \"-pw\", \"-ssh\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n /* RDP port and usual SSH tunneling related switches in command line */\n process.args : \"*:3389\" and\n process.args : (\"-L\", \"-P\", \"-R\", \"-pw\", \"-ssh\")\n", "references": [ "https://blog.netspi.com/how-to-access-rdp-over-a-reverse-ssh-tunnel/" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json index 44d36351afbc96..d9427ca3415984 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json index 9d202cf61243d3..f6d612b73dc385 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json index 109ed483653f55..42472277b20c55 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json @@ -11,10 +11,11 @@ "language": "kuery", "license": "Elastic License", "name": "Remote SSH Login Enabled via systemsetup Command", - "query": "event.category:process and event.type:(start or process_started) and process.name:systemsetup and process.args:(\"-f\" and \"-setremotelogin\" and on)", + "query": "event.category:process and event.type:(start or process_started) and process.name:systemsetup and process.args:(\"-setremotelogin\" and on)", "references": [ "https://documents.trendmicro.com/assets/pdf/XCSSET_Technical_Brief.pdf", - "https://ss64.com/osx/systemsetup.html" + "https://ss64.com/osx/systemsetup.html", + "https://support.apple.com/guide/remote-desktop/about-systemsetup-apd95406b8d/mac" ], "risk_score": 47, "rule_id": "5ae4e6f8-d1bf-40fa-96ba-e29645e1e4dc", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json index ca29829849a0e5..245a6b4baf8d80 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -56,5 +57,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json index 522b57a1b7966e..9da368ca4e9140 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious RDP ActiveX Client Loaded", - "query": "library where file.name == \"mstscax.dll\" and\n /* depending on noise in your env add here extra paths */\n wildcard(process.executable, \"C:\\\\Windows\\\\*\",\n \"C:\\\\Users\\\\Public\\\\*\",\n \"C:\\\\Users\\\\Default\\\\*\",\n \"C:\\\\Intel\\\\*\",\n \"C:\\\\PerfLogs\\\\*\",\n \"C:\\\\ProgramData\\\\*\",\n \"\\\\Device\\\\Mup\\\\*\",\n \"\\\\\\\\*\") and\n /* add here FPs */\n not process.executable in (\"C:\\\\Windows\\\\System32\\\\mstsc.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\mstsc.exe\")\n", + "query": "library where dll.name : \"mstscax.dll\" and\n /* depending on noise in your env add here extra paths */\n process.executable :\n (\n \"C:\\\\Windows\\\\*\",\n \"C:\\\\Users\\\\Public\\\\*\",\n \"C:\\\\Users\\\\Default\\\\*\",\n \"C:\\\\Intel\\\\*\",\n \"C:\\\\PerfLogs\\\\*\",\n \"C:\\\\ProgramData\\\\*\",\n \"\\\\Device\\\\Mup\\\\*\",\n \"\\\\\\\\*\"\n ) and\n /* add here FPs */\n not process.executable : (\"C:\\\\Windows\\\\System32\\\\mstsc.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\mstsc.exe\")\n", "references": [ "https://posts.specterops.io/revisiting-remote-desktop-lateral-movement-8fb905cb46c3" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json index c309f6dbdde29a..1f01a2c88fb09f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -66,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json new file mode 100644 index 00000000000000..e3cd83fc44c04e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands to connect to an existing Virtual Private Network (VPN).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Virtual Private Network Connection Attempt", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n (process.name : \"networksetup\" and process.args : \"-connectpppoeservice\") or\n (process.name : \"scutil\" and process.args : \"--nc\" and process.args : \"start\") or\n (process.name : \"osascript\" and process.command_line : \"osascript*set VPN to service*\")\n )\n", + "references": [ + "https://github.com/rapid7/metasploit-framework/blob/master/modules/post/osx/manage/vpn.rb", + "https://www.unix.com/man-page/osx/8/networksetup/", + "https://superuser.com/questions/358513/start-configured-vpn-from-command-line-osx" + ], + "risk_score": 21, + "rule_id": "15dacaa0-5b90-466b-acab-63435a59701a", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json new file mode 100644 index 00000000000000..3d1f1cf63d5b82 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to create a local account that will be hidden from the macOS logon window. This may indicate an attempt to evade user attention while maintaining persistence using a separate local account.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Hidden Local User Account Creation", + "query": "event.category:process and event.type:(start or process_started) and process.name:dscl and process.args:(IsHidden and create and (true or 1 or yes))", + "references": [ + "https://support.apple.com/en-us/HT203998" + ], + "risk_score": 47, + "rule_id": "41b638a1-8ab6-4f8e-86d9-466317ef2db5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json index 5f569781c2d499..87e2a956888f22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json index 3e7bfb9f46ce5a..b0704dd78a51de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Installation of Custom Shim Databases", - "query": "sequence by process.entity_id with maxspan=5m\n [process where event.type in (\"start\", \"process_started\") and\n not (process.name : \"sdbinst.exe\" and process.parent.name : \"msiexec.exe\")]\n [registry where event.type in (\"creation\", \"change\") and\n wildcard(registry.path, \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Custom\\\\*.sdb\")]\n", + "query": "sequence by process.entity_id with maxspan = 5m\n [process where event.type in (\"start\", \"process_started\") and\n not (process.name : \"sdbinst.exe\" and process.parent.name : \"msiexec.exe\")]\n [registry where event.type in (\"creation\", \"change\") and\n registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Custom\\\\*.sdb\"]\n", "risk_score": 21, "rule_id": "c5ce48a6-7f57-4ee8-9313-3d0024caee10", "severity": "medium", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json index 1e8f5b339ba608..393e593bed4825 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Detects attempts to maintain persistence by creating registry keys using AppCert DLLs. AppCert DLLs are loaded by every process using the common API functions to create processes.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json index b5be845a50e66e..21310499ecbd7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Attackers may maintain persistence by creating registry keys using AppInit DLLs. AppInit DLLs are loaded by every process using the common library, user32.dll.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json new file mode 100644 index 00000000000000..e105b91362adb1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json @@ -0,0 +1,75 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of osascript to create a hidden login item. This may indicate an attempt to persist a malicious program while concealing its presence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of Hidden Login Item via Apple Script", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*login item*hidden:true*\"\n", + "risk_score": 47, + "rule_id": "f24bcae1-8980-4b30-b5dd-f851b055c9e7", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.011", + "name": "Plist Modification", + "reference": "https://attack.mitre.org/techniques/T1547/011/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.002", + "name": "AppleScript", + "reference": "https://attack.mitre.org/techniques/T1059/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json new file mode 100644 index 00000000000000..1445d9c489dba0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Authorization plugins are used to extend the authorization services API and implement mechanisms that are not natively supported by the OS, such as multi-factor authentication with third party software. Adversaries may abuse this feature to persist and/or collect clear text credentials as they traverse the registered plugins during user logon.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Authorization Plugin Modification", + "query": "event.category:file and not event.type:deletion and file.path:(/Library/Security/SecurityAgentPlugins/* and not /Library/Security/SecurityAgentPlugins/TeamViewerAuthPlugin.bundle/Contents/*)", + "references": [ + "https://developer.apple.com/documentation/security/authorization_plug-ins", + "https://www.xorrior.com/persistent-credential-theft/" + ], + "risk_score": 47, + "rule_id": "e6c98d38-633d-4b3e-9387-42112cd5ac10", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json new file mode 100644 index 00000000000000..2d250ede2832b8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json @@ -0,0 +1,71 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may modify the standard authentication module for persistence via patching the normal authorization process or modifying the login configuration to allow unauthorized access or elevate privileges.", + "false_positives": [ + "Trusted system module updates or allowed Pluggable Authentication Module (PAM) daemon configuration changes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Standard Authentication Module or Configuration", + "query": "event.category:file and event.type:change and (file.name:pam_*.so or file.path:(/etc/pam.d/* or /private/etc/pam.d/*)) and process.executable: (* and not ( /bin/yum or \"/usr/sbin/pam-auth-update\" or /usr/libexec/packagekitd or /usr/bin/dpkg or /usr/bin/vim or /usr/libexec/xpcproxy or /usr/bin/bsdtar or /usr/local/bin/brew ) )", + "references": [ + "https://github.com/zephrax/linux-pam-backdoor", + "https://github.com/eurialo/pambd", + "http://0x90909090.blogspot.com/2016/06/creating-backdoor-in-pam-in-5-line-of.html", + "https://www.trendmicro.com/en_us/research/19/i/skidmap-linux-malware-uses-rootkit-capabilities-to-hide-cryptocurrency-mining-payload.html" + ], + "risk_score": 47, + "rule_id": "93f47b6f-5728-4004-ba00-625083b3dcb0", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Credential Access", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json new file mode 100644 index 00000000000000..8b9df12761d20c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json @@ -0,0 +1,67 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may modify SSH related binaries for persistence or credential access by patching sensitive functions to enable unauthorized access or by logging SSH credentials for exfiltration.", + "false_positives": [ + "Trusted OpenSSH executable updates. It's recommended to verify the integrity of OpenSSH binary changes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of OpenSSH Binaries", + "query": "event.category:file and event.type:change and process.name:* and (file.path:(/usr/sbin/sshd or /usr/bin/ssh or /usr/bin/sftp or /usr/bin/scp) or file.name:libkeyutils.so) and not process.executable:/usr/bin/dpkg", + "references": [ + "https://blog.angelalonso.es/2016/09/anatomy-of-real-linux-intrusion-part-ii.html" + ], + "risk_score": 47, + "rule_id": "0415f22a-2336-45fa-ba07-618a5942e22c", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Credential Access", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json new file mode 100644 index 00000000000000..7969feb7e204c9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or execution of a cron job. Adversaries may abuse cron jobs to perform task scheduling for initial or recurring execution of malicious code.", + "false_positives": [ + "Legitimate software or scripts using cron jobs for recurring tasks." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Cron Job", + "query": "event.category:process and event.type:(start or process_started or info) and not user.name:root and ((process.name:crontab and not process.args:(\"-l\" or \"-r\" or \"-e\" or \"-help\" or \"-h\")) or (process.parent.name:cron and not process.name:\"running job\" and not process.executable:(/Applications/Docker.app/Contents/Resources/bin/docker or /usr/bin/killall or /usr/sbin/sendmail or /usr/bin/env or /usr/bin/timeshift or /bin/rm)))", + "references": [ + "https://archive.f-secure.com/weblog/archives/00002576.html", + "https://ss64.com/osx/crontab.html" + ], + "risk_score": 21, + "rule_id": "b1c14366-f4f8-49a0-bcbb-51d2de8b0bb8", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json new file mode 100644 index 00000000000000..1c2628871b8d08 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json @@ -0,0 +1,80 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a launchd child process with a hidden file. An adversary can establish persistence by installing a new logon item, launch agent, or daemon that executes upon login.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Hidden Child Process of Launchd", + "query": "event.category:process and event.type:(start or process_started) and process.name:.* and process.parent.executable:/sbin/launchd", + "references": [ + "https://objective-see.com/blog/blog_0x61.html", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/", + "https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" + ], + "risk_score": 47, + "rule_id": "083fa162-e790-4d85-9aeb-4fea04188adb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.001", + "name": "Launch Agent", + "reference": "https://attack.mitre.org/techniques/T1543/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.001", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1564/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json new file mode 100644 index 00000000000000..8fe0648e8e5a98 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a DirectoryService PlugIns (dsplug) file. The DirectoryService daemonlaunches on each system boot and automatically reloads after crash. It scans and executes bundles that are located in the DirectoryServices PlugIns folder and can be abused by adversaries to maintain persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Persistence via DirectoryService Plugin Modification", + "query": "event.category:file and not event.type:deletion and file.path:/Library/DirectoryServices/PlugIns/*.dsplug", + "references": [ + "https://blog.chichou.me/2019/11/21/two-macos-persistence-tricks-abusing-plugins/" + ], + "risk_score": 47, + "rule_id": "89fa6cb7-6b53-4de2-b604-648488841ab8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json new file mode 100644 index 00000000000000..096ebc04ddeb5c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "An adversary can establish persistence by modifying an existing macOS dock property list in order to execute a malicious application instead of the intended one when invoked.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Persistence via Docker Shortcut Modification", + "query": "event.category : file and event.action : modification and file.path : /Users/*/Library/Preferences/com.apple.dock.plist and not process.name : (xpcproxy or cfprefsd or plutil or jamf or PlistBuddy or InstallerRemotePluginService)", + "references": [ + "https://github.com/specterops/presentations/raw/master/Leo Pitt/Hey_Im_Still_in_Here_Modern_macOS_Persistence_SO-CON2020.pdf" + ], + "risk_score": 47, + "rule_id": "c81cefcb-82b9-4408-a533-3c3df549e62d", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json new file mode 100644 index 00000000000000..71375e835cf414 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the Event Monitor Daemon (emond) rules. Adversaries may abuse this service by writing a rule to execute commands when a defined event occurs, such as system start up or user authentication.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Emond Rules Creation or Modification", + "query": "file where event.type != \"deletion\" and\n file.path : (\"/private/etc/emond.d/rules/*.plist\", \"/etc/emon.d/rules/*.plist\")\n", + "references": [ + "https://www.xorrior.com/emond-persistence/" + ], + "risk_score": 47, + "rule_id": "a6bf4dd4-743e-4da8-8c03-3ebd753a6c90", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.014", + "name": "Emond", + "reference": "https://attack.mitre.org/techniques/T1546/014/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json new file mode 100644 index 00000000000000..62e93786786b48 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a suspicious child process of the Event Monitor Daemon (emond). Adversaries may abuse this service by writing a rule to execute commands when a defined event occurs, such as system start up or user authentication.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Emond Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"emond\" and\n process.name : (\n \"bash\",\n \"dash\",\n \"sh\",\n \"tcsh\",\n \"csh\",\n \"zsh\",\n \"ksh\",\n \"fish\",\n \"Python\",\n \"python*\",\n \"perl*\",\n \"php*\",\n \"osascript\",\n \"pwsh\",\n \"curl\",\n \"wget\",\n \"cp\",\n \"mv\",\n \"touch\",\n \"echo\",\n \"base64\",\n \"launchctl\")\n", + "references": [ + "https://www.xorrior.com/emond-persistence/" + ], + "risk_score": 47, + "rule_id": "3e3d15c6-1509-479a-b125-21718372157e", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.014", + "name": "Emond", + "reference": "https://attack.mitre.org/techniques/T1546/014/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json new file mode 100644 index 00000000000000..e7c27e8d33caf3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to enable the root account using the dsenableroot command. This command may be abused by adversaries for persistence, as the root account is disabled by default.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Enable the Root Account", + "query": "event.category:process and event.type:(start or process_started) and process.name:dsenableroot and not process.args:\"-d\"", + "references": [ + "https://ss64.com/osx/dsenableroot.html" + ], + "risk_score": 47, + "rule_id": "cc2fd2d0-ba3a-4939-b87f-2901764ed036", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json new file mode 100644 index 00000000000000..4129a18994f112 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a hidden launch agent or daemon. An adversary may establish persistence by installing a new launch agent or daemon which executes at login.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of Hidden Launch Agent or Daemon", + "query": "file where event.type != \"deletion\" and\n file.path : \n (\n \"/System/Library/LaunchAgents/.*.plist\",\n \"/Library/LaunchAgents/.*.plist\",\n \"/Users/*/Library/LaunchAgents/.*.plist\",\n \"/System/Library/LaunchDaemons/.*.plist\",\n \"/Library/LaunchDaemons/.*.plist\"\n )\n", + "references": [ + "https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" + ], + "risk_score": 47, + "rule_id": "092b068f-84ac-485d-8a55-7dd9e006715f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.001", + "name": "Launch Agent", + "reference": "https://attack.mitre.org/techniques/T1543/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.001", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1564/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json new file mode 100644 index 00000000000000..bfe3c1f20cb812 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a hidden local user account by appending the dollar sign to the account name. This is sometimes done by attackers to increase access to a system and avoid appearing in the results of accounts listing using the net users command.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of a Hidden Local User Account", + "query": "registry where registry.path : \"HKLM\\\\SAM\\\\SAM\\\\Domains\\\\Account\\\\Users\\\\Names\\\\*$\\\\\"\n", + "references": [ + "https://blog.menasec.net/2019/02/threat-hunting-6-hiding-in-plain-sights_8.html", + "https://github.com/CyberMonitor/APT_CyberCriminal_Campagin_Collections/tree/master/2020/2020.12.15.Lazarus_Campaign" + ], + "risk_score": 73, + "rule_id": "2edc8076-291e-41e9-81e4-e3fcbc97ae5e", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json index 03cb315a9982ff..9e83f609ac830a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Debugger and SilentProcessExit registry keys can allow an adversary to intercept the execution of files, causing a different process to be executed. This functionality can be abused by an adversary to establish persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json new file mode 100644 index 00000000000000..0fa1058918a194 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Finder Sync plugins enable users to extend Finder\u2019s functionality by modifying the user interface. Adversaries may abuse this feature by adding a rogue Finder Plugin to repeatedly execute malicious payloads for persistence.", + "false_positives": [ + "Trusted Finder Sync Plugins" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Finder Sync Plugin Registered and Enabled", + "query": "sequence by host.id, user.id with maxspan = 5s\n [process where event.type in (\"start\", \"process_started\") and process.name : \"pluginkit\" and process.args : \"-a\"]\n [process where event.type in (\"start\", \"process_started\") and process.name : \"pluginkit\" and\n process.args : \"-e\" and process.args : \"use\" and process.args : \"-i\" and\n not process.args :\n (\n \"com.google.GoogleDrive.FinderSyncAPIExtension\",\n \"com.google.drivefs.findersync\",\n \"com.boxcryptor.osx.Rednif\",\n \"com.adobe.accmac.ACCFinderSync\",\n \"com.microsoft.OneDrive.FinderSync\",\n \"com.insynchq.Insync.Insync-Finder-Integration\",\n \"com.box.desktop.findersyncext\"\n )\n ]\n", + "references": [ + "https://github.com/specterops/presentations/raw/master/Leo Pitt/Hey_Im_Still_in_Here_Modern_macOS_Persistence_SO-CON2020.pdf" + ], + "risk_score": 47, + "rule_id": "37f638ea-909d-4f94-9248-edd21e4a9906", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json index 17d2505f4aaca3..c3eb9584b7dfb2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json new file mode 100644 index 00000000000000..2ecb3624e4fcaf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a K Desktop Environment (KDE) AutoStart script or desktop file that will execute upon each user logon. Adversaries may abuse this method for persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Persistence via KDE AutoStart Script or Desktop File Modification", + "query": "file where event.type != \"deletion\" and\n file.extension in (\"sh\", \"desktop\") and\n file.path :\n (\n \"/home/*/.config/autostart/*\", \"/root/.config/autostart/*\",\n \"/home/*/.kde/Autostart/*\", \"/root/.kde/Autostart/*\",\n \"/home/*/.kde4/Autostart/*\", \"/root/.kde4/Autostart/*\",\n \"/home/*/.kde/share/autostart/*\", \"/root/.kde/share/autostart/*\",\n \"/home/*/.kde4/share/autostart/*\", \"/root/.kde4/share/autostart/*\",\n \"/home/*/.local/share/autostart/*\", \"/root/.local/share/autostart/*\",\n \"/home/*/.config/autostart-scripts/*\", \"/root/.config/autostart-scripts/*\",\n \"/etc/xdg/autostart/*\", \"/usr/share/autostart/*\"\n )\n", + "references": [ + "https://userbase.kde.org/System_Settings/Autostart", + "https://www.amnesty.org/en/latest/research/2020/09/german-made-finspy-spyware-found-in-egypt-and-mac-and-linux-versions-revealed/", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/" + ], + "risk_score": 47, + "rule_id": "e3e904b3-0a8e-4e68-86a8-977a163e21d3", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json index 3f1216c2fcc33d..da5967d6d45965 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json index ad28885de07404..69e2eee696cb39 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json @@ -9,14 +9,15 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Scheduled Task Created by a Windows Script", "note": "Decode the base64 encoded Tasks Actions registry value to investigate the task's configured action.", - "query": "sequence by host.id with maxspan = 30s\n [library where file.name : \"taskschd.dll\" and process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\")]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", - "risk_score": 43, + "query": "sequence by host.id with maxspan = 30s\n [library where dll.name : \"taskschd.dll\" and process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\")]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", + "risk_score": 47, "rule_id": "689b9d57-e4d5-4357-ad17-9c334609d79a", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json new file mode 100644 index 00000000000000..22d82b567f362e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the login window property list (plist). Adversaries may modify plist files to run a program during system boot or user login for persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Login Hook", + "note": "Starting in Mac OS X 10.7 (Lion), users can specify certain applications to be re-opened when a user reboots their machine. This can be abused to establish or maintain persistence on a compromised system.", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.name:\"com.apple.loginwindow.plist\" and process.name:(* and not (systemmigrationd or DesktopServicesHelper or diskmanagementd or rsync or launchd or cfprefsd or xpcproxy or ManagedClient or MCXCompositor))", + "references": [ + "https://github.com/D00MFist/PersistentJXA/blob/master/LoginScript.js" + ], + "risk_score": 47, + "rule_id": "ac412404-57a5-476f-858f-4e8fbb4f48d8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.011", + "name": "Plist Modification", + "reference": "https://attack.mitre.org/techniques/T1547/011/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json new file mode 100644 index 00000000000000..64d2b7834c95a9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may create or modify the Sublime application plugins or scripts to execute a malicious payload each time the Sublime application is started.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Sublime Plugin or Application Script Modification", + "query": "file where event.type in (\"change\", \"creation\") and file.extension : \"py\" and\n file.path : \n (\n \"/Users/*/Library/Application Support/Sublime Text*/Packages/*.py\", \n \"/Applications/Sublime Text.app/Contents/MacOS/sublime.py\"\n ) and\n not process.executable : \n (\n \"/Applications/Sublime Text*.app/Contents/MacOS/Sublime Text*\", \n \"/usr/local/Cellar/git/*/bin/git\", \n \"/usr/libexec/xpcproxy\", \n \"/System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper\", \n \"/Applications/Sublime Text.app/Contents/MacOS/plugin_host\"\n )\n", + "references": [ + "https://posts.specterops.io/persistent-jxa-66e1c3cd1cf5" + ], + "risk_score": 21, + "rule_id": "88817a33-60d3-411f-ba79-7c905d865b2a", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1554", + "name": "Compromise Client Software Binary", + "reference": "https://attack.mitre.org/techniques/T1554/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json index 573ff7f7b43310..7da9b515b64579 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json @@ -6,16 +6,17 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Persistence via Microsoft Office AddIns", - "query": "file where event.type != \"deletion\" and\n wildcard(file.extension,\"wll\",\"xll\",\"ppa\",\"ppam\",\"xla\",\"xlam\") and\n wildcard(file.path, \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Word\\\\Startup\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\AddIns\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Excel\\\\XLSTART\\\\*\")\n", + "query": "file where event.type != \"deletion\" and\n file.extension : (\"wll\",\"xll\",\"ppa\",\"ppam\",\"xla\",\"xlam\") and\n file.path :\n (\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Word\\\\Startup\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\AddIns\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Excel\\\\XLSTART\\\\*\"\n )\n", "references": [ "https://labs.mwrinfosecurity.com/blog/add-in-opportunities-for-office-persistence/" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "f44fa4b6-524c-4e87-8d9e-a32599e4fb7c", "severity": "high", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json index 9192ea9ab3961d..1c1eddeb91a9ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json @@ -9,17 +9,18 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Persistence via Microsoft Outlook VBA", - "query": "file where event.type != \"deletion\" and\n wildcard(file.path, \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Outlook\\\\VbaProject.OTM\")\n", + "query": "file where event.type != \"deletion\" and\n file.path : \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Outlook\\\\VbaProject.OTM\"\n", "references": [ "https://www.mdsec.co.uk/2020/11/a-fresh-outlook-on-mail-based-persistence/", "https://www.linkedin.com/pulse/outlook-backdoor-using-vba-samir-b-/" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "397945f3-d39a-4e6f-8bcb-9656c2031438", "severity": "medium", "tags": [ @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json new file mode 100644 index 00000000000000..e54b368a24cc26 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the default configuration for periodic tasks. Adversaries may abuse periodic tasks to execute malicious code or maintain persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Periodic Tasks", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:(/private/etc/periodic/* or /private/etc/defaults/periodic.conf or /private/etc/periodic.conf)", + "references": [ + "https://opensource.apple.com/source/crontabs/crontabs-13/private/etc/defaults/periodic.conf.auto.html", + "https://www.oreilly.com/library/view/mac-os-x/0596003706/re328.html", + "https://github.com/D00MFist/PersistentJXA/blob/master/PeriodicPersist.js" + ], + "risk_score": 21, + "rule_id": "48ec9452-e1fd-4513-a376-10a1a26d2c83", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json index 1cd66ea45dea36..69d84e6082f7ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Windows contains accessibility features that may be launched with a key combination before a user has logged in. An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json index 93e67ccab04b2d..52ca728d933f59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Detects changes to registry persistence keys that are uncommonly used or modified by legitimate programs. This could be an indication of an adversary's attempt to persist in a stealthy manner.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json index 62ca418bbfdecd..2e06beaa4e32b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies run key or startup key registry modifications. In order to survive reboots and other system interrupts, attackers will modify run keys within the registry or leverage startup folder items as a form of persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json index 52d0720839f5cd..c4c1c1f23b6a58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies execution of suspicious persistent programs (scripts, rundll32, etc.) by looking at process lineage and command line usage.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json index 8d90717ec69fc0..f95d6e883adf2d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies processes modifying the services registry key directly, instead of through the expected Windows APIs. This could be an indication of an adversary attempting to stealthily persist through abnormal service creation or modification of an existing service.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json new file mode 100644 index 00000000000000..78fa1e65d1e0c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Both ~/.bash_profile and ~/.bashrc are files containing shell commands that are run when Bash is invoked. These files are executed in a user's context, either interactively or non-interactively, when a user logs in so that their environment is set correctly. Adversaries may abuse this to establish persistence by executing malicious content triggered by a user\u2019s shell.", + "false_positives": [ + "Changes to the Shell Profile tend to be noisy, a tuning per your environment will be required." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Bash Shell Profile Modification", + "query": "event.category:file and event.type:change and process.name:(* and not (sudo or vim or zsh or env or nano or bash or Terminal or xpcproxy or login or cat or cp or launchctl or java)) and not process.executable:(/Applications/* or /private/var/folders/* or /usr/local/*) and file.path:(/private/etc/rc.local or /etc/rc.local or /home/*/.profile or /home/*/.profile1 or /home/*/.bash_profile or /home/*/.bash_profile1 or /home/*/.bashrc or /Users/*/.bash_profile or /Users/*/.zshenv)", + "references": [ + "https://www.anomali.com/blog/pulling-linux-rabbit-rabbot-malware-out-of-a-hat" + ], + "risk_score": 47, + "rule_id": "e6c1a552-7776-44ad-ae0f-8746cc07773c", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Execution", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.004", + "name": ".bash_profile and .bashrc", + "reference": "https://attack.mitre.org/techniques/T1546/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json new file mode 100644 index 00000000000000..aa24a832e594ca --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "The Secure Shell (SSH) authorized_keys file specifies which users are allowed to log into a server using public key authentication. Adversaries may modify it to maintain persistence on a victim host by adding their own public key(s).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SSH Authorized Keys File Modification", + "query": "event.category:file and event.type:(change or creation) and file.name:(\"authorized_keys\" or \"authorized_keys2\") and not process.executable: (/Library/Developer/CommandLineTools/usr/bin/git or /usr/local/Cellar/maven/*/libexec/bin/mvn or /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or /usr/bin/vim or /usr/local/Cellar/coreutils/*/bin/gcat or /usr/bin/bsdtar or /usr/bin/nautilus or /usr/bin/scp or /usr/bin/touch or /var/lib/docker/*)", + "risk_score": 47, + "rule_id": "2215b8bd-1759-4ffa-8ab8-55c8e6b32e7f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.004", + "name": "SSH Authorized Keys", + "reference": "https://attack.mitre.org/techniques/T1098/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json index 5defde988ac3b4..c202a416c3202e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies files written to or modified in the startup folder by commonly abused processes. Adversaries may use this technique to maintain persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json index 67c9c3db6ba2aa..bf58c0f084baa4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies files written or modified in the startup folder by unsigned processes. Adversaries may abuse this technique to maintain persistence in an environment.", + "from": "now-9m", "index": [ "logs-endpoint.events.*" ], @@ -45,5 +46,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json index f689796a3673ed..6ea42440c3f91c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies script engines creating files in the startup folder, or the creation of script files in the startup folder.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json new file mode 100644 index 00000000000000..e454dac4dba99c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious modifications of the calendar file by an unusual process. Adversaries may create a custom calendar notification procedure to execute a malicious program at a recurring interval to establish persistence.", + "false_positives": [ + "Trusted applications for managing calendars and reminders." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Calendar File Modification", + "query": "event.category:file and event.action:modification and file.path:/Users/*/Library/Calendars/*.calendar/Events/*.ics and process.executable: (* and not ( /System/Library/* or /System/Applications/Calendar.app/Contents/MacOS/* or /usr/libexec/xpcproxy or /sbin/launchd or /Applications/* ) )", + "references": [ + "https://labs.f-secure.com/blog/operationalising-calendar-alerts-persistence-on-macos", + "https://github.com/FSecureLABS/CalendarPersist", + "https://github.com/D00MFist/PersistentJXA/blob/master/CalendarPersist.js" + ], + "risk_score": 47, + "rule_id": "cb71aa62-55c8-42f0-b0dd-afb0bb0b1f51", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index 41e8bd04b87ef3..7a7f0906b65b9b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies Component Object Model (COM) hijacking via registry modification. Adversaries may establish persistence by executing malicious content triggered by hijacked references to COM objects.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json index 77e63a546a896c..856ed7127aa9d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json @@ -3,14 +3,16 @@ "Elastic" ], "description": "Identifies a suspicious image load (taskschd.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where a scheduled task is configured via Windows Component Object Model (COM). This technique can be used to configure persistence and evade monitoring by avoiding the usage of the traditional Windows binary (schtasks.exe) used to manage scheduled tasks.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Image Load (taskschd.dll) from MS Office", - "query": "library where process.name in (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action == \"load\" and\n event.category == \"library\" and\n file.name == \"taskschd.dll\"\n", + "query": "library where process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action : \"load\" and\n event.category : \"library\" and\n dll.name : \"taskschd.dll\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", "https://www.clearskysec.com/wp-content/uploads/2020/10/Operation-Quicksand.pdf" @@ -44,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json index 51d19bdaef6dbd..0ccec7f29805b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json @@ -9,13 +9,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Execution via Scheduled Task", "query": "process where event.type == \"start\" and\n /* Schedule service cmdline on Win10+ */\n process.parent.name : \"svchost.exe\" and process.parent.args : \"Schedule\" and\n /* add suspicious programs here */\n process.pe.original_file_name in\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) and\n /* add suspicious paths here */\n process.args : (\n \"C:\\\\Users\\\\*\",\n \"C:\\\\ProgramData\\\\*\", \n \"C:\\\\Windows\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\Tasks\\\\*\", \n \"C:\\\\PerfLogs\\\\*\", \n \"C:\\\\Intel\\\\*\", \n \"C:\\\\Windows\\\\Debug\\\\*\", \n \"C:\\\\HP\\\\*\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "5d1d6907-0747-4d5d-9b24-e4a18853dc0a", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json index 487327f6344bad..a9cba3e9be599b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies the creation of a suspicious ImagePath value. This could be an indication of an adversary attempting to stealthily persist or escalate privileges through abnormal service creation.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json index 33198e716af0de..80a0e067aa26f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json new file mode 100644 index 00000000000000..72310046bf35c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows operating systems are utilizing the time provider architecture in order to obtain accurate time stamps from other network devices or clients in the network. Time providers are implemented in the form of a DLL file which resides in System32 folder. The service W32Time initiates during the startup of Windows and loads w32time.dll. Adversaries may abuse this architecture to establish persistence, specifically by registering and enabling a malicious DLL as a time provider.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Persistence via Time Provider Modification", + "query": "registry where event.type:\"change\" and\n registry.path:\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\W32Time\\\\TimeProviders\\\\*\" and\n registry.data.strings:\"*.dll\"\n", + "references": [ + "https://pentestlab.blog/2019/10/22/persistence-time-providers/" + ], + "risk_score": 47, + "rule_id": "14ed1aa9-ebfd-4cf9-a463-0ac59ec55204", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.003", + "name": "Time Providers", + "reference": "https://attack.mitre.org/techniques/T1547/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json new file mode 100644 index 00000000000000..7891df2ca55882 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic", + "Skoetting" + ], + "description": "Identifies a user being added to a privileged group in Active Directory. Privileged accounts and groups in Active Directory are those to which powerful rights, privileges, and permissions are granted that allow them to perform nearly any action in Active Directory and on domain-joined systems.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "User Added to Privileged Group in Active Directory", + "query": "event.category:iam and event.action:\"added-member-to-group\" and group.name:(Administrators or \"Local Administrators\" or \"Domain Admins\" or \"Enterprise Admins\" or \"Backup Admins\" or \"Schema Admins\" or \"DnsAdmins\")", + "references": [ + "https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-b--privileged-accounts-and-groups-in-active-directory" + ], + "risk_score": 21, + "rule_id": "5cd8e1f7-0050-4afc-b2df-904e40b2f5ae", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/", + "subtechnique": [ + { + "id": "T1136.001", + "name": "Local Account", + "reference": "https://attack.mitre.org/techniques/T1136/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index cbc8ea15bb800f..cb9c70a842b2df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json index a9ca3e2c8da488..f910ae13d92cdd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Application Shim was created to allow for backward compatibility of software as the operating system codebase changes over time. This Windows functionality has been abused by attackers to stealthily gain persistence and arbitrary code execution in legitimate Windows processes.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -69,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json new file mode 100644 index 00000000000000..c2ae7b2ff335d8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json @@ -0,0 +1,32 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the Atom desktop text editor Init File. Adversaries may add malicious JavaScript code to the init.coffee file that will be executed upon the Atom application opening.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Atom Init Script Modification", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:/Users/*/.atom/init.coffee and not process.name:(Atom or xpcproxy) and not user.name:root", + "references": [ + "https://github.com/D00MFist/PersistentJXA/blob/master/AtomPersist.js", + "https://flight-manual.atom.io/hacking-atom/sections/the-init-file/" + ], + "risk_score": 21, + "rule_id": "b4449455-f986-4b5a-82ed-e36b129331f7", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json index 23b316fad1db5d..3fe0137ec8efb0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json index ea861b2634d837..983c33ca288ac4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies registry modifications related to the Windows Security Support Provider (SSP) configuration. Adversaries may abuse this to establish persistence in an environment.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json index 71114206fb47df..4543def3253ad8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json index 1ed0077a66529f..bcfd0e6b94ac6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], - "language": "kuery", + "language": "eql", "license": "Elastic License", "name": "Persistence via Update Orchestrator Service Hijack", - "query": "event.category:process and event.type:(start or process_started) and process.parent.name:svchost.exe and process.parent.args:(UsoSvc or usosvc) and not process.name:(UsoClient.exe or usoclient.exe or MusNotification.exe or musnotification.exe or MusNotificationUx.exe or musnotificationux.exe)", + "query": "process where event.type == \"start\" and\n process.parent.executable : \"C:\\\\Windows\\\\System32\\\\svchost.exe\" and\n process.parent.args : \"UsoSvc\" and\n not process.executable :\n (\n \"C:\\\\Windows\\\\System32\\\\UsoClient.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotification.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotificationUx.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotifyIcon.exe\",\n \"C:\\\\Windows\\\\System32\\\\WerFault.exe\",\n \"C:\\\\Windows\\\\System32\\\\WerMgr.exe\"\n )\n", "references": [ "https://github.com/irsl/CVE-2020-1313" ], @@ -50,6 +51,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 3 + "type": "eql", + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json index 17453925b3b3bf..d7bc2c41f95047 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "An adversary can use Windows Management Instrumentation (WMI) to install event filters, providers, consumers, and bindings that execute code when a defined event occurs. Adversaries may use the capabilities of WMI to subscribe to an event and execute arbitrary code when that event occurs, providing persistence on a system.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json new file mode 100644 index 00000000000000..36faa1a5ed87c5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies execution of the Apple script interpreter (osascript) without a password prompt and with administrator privileges.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Apple Scripting Execution with Administrator Privileges", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*with administrator privileges\"\n", + "references": [ + "https://discussions.apple.com/thread/2266150" + ], + "risk_score": 47, + "rule_id": "827f8d8f-4117-4ae4-b551-f56d54b9da6b", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json new file mode 100644 index 00000000000000..b600fe5dc99508 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json @@ -0,0 +1,80 @@ +{ + "author": [ + "Elastic" + ], + "description": "User Account Control (UAC) can help mitigate the impact of malware on Windows hosts. With UAC, apps and tasks always run in the security context of a non-administrator account, unless an administrator specifically authorizes administrator-level access to the system. This rule identifies registry value changes to bypass User Access Control (UAC) protection.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Disabling User Account Control via Registry Modification", + "query": "registry where event.type == \"change\" and\n registry.path :\n (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\EnableLUA\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\ConsentPromptBehaviorAdmin\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\PromptOnSecureDesktop\"\n ) and\n registry.data.strings : \"0\"\n", + "references": [ + "https://www.greyhathacker.net/?p=796", + "https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-group-policy-and-registry-key-settings", + "https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-overview" + ], + "risk_score": 47, + "rule_id": "d31f183a-e5b1-451b-8534-ba62bca0b404", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.002", + "name": "Bypass User Access Control", + "reference": "https://attack.mitre.org/techniques/T1548/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.002", + "name": "Bypass User Access Control", + "reference": "https://attack.mitre.org/techniques/T1548/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json new file mode 100644 index 00000000000000..b5cd27e6ff02b9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "A sudoers file specifies the commands users or groups can run and from which terminals. Adversaries can take advantage of these configurations to execute commands as other users or spawn processes with higher privileges.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Privilege Escalation via Sudoers File Modification", + "query": "event.category:process and event.type:start and process.args:(echo and *NOPASSWD*ALL*)", + "risk_score": 73, + "rule_id": "76152ca1-71d0-4003-9e37-0983e12832da", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.003", + "name": "Sudo and Sudo Caching", + "reference": "https://attack.mitre.org/techniques/T1548/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json index 1b741cd1a8c97c..ff735cbb40e42e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json @@ -2,16 +2,16 @@ "author": [ "Elastic" ], - "description": "Identifies execution of the security_authtrampoline process via the Apple script interpreter (osascript). This occurs when programs use AuthorizationExecute-WithPrivileges from the Security.framework to run another program with root privileges. It should not be run by itself, as this is a sign of execution with explicit logon credentials.", + "description": "Identifies execution of the security_authtrampoline process via a scripting interpreter. This occurs when programs use AuthorizationExecute-WithPrivileges from the Security.framework to run another program with root privileges. It should not be run by itself, as this is a sign of execution with explicit logon credentials.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "eql", + "language": "kuery", "license": "Elastic License", - "name": "Execution with Explicit Credentials via Apple Scripting", - "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"osascript\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name == \"security_authtrampoline\"] by process.ppid\n", + "name": "Execution with Explicit Credentials via Scripting", + "query": "event.category:process and event.type:(start or process_started) and process.name:\"security_authtrampoline\" and process.parent.name:(osascript or com.apple.automator.runner or sh or bash or dash or zsh or python* or perl* or php* or ruby or pwsh)", "references": [ "https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf", "https://www.manpagez.com/man/8/security_authtrampoline/" @@ -59,6 +59,7 @@ ] } ], - "type": "eql", - "version": 1 + "timestamp_override": "event.ingested", + "type": "query", + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json new file mode 100644 index 00000000000000..f80e877bcb43c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects attempts to exploit privilege escalation vulnerabilities related to the Adobe Acrobat Reader PrivilegedHelperTool responsible for installing updates. For more information, refer to CVE-2020-9615, CVE-2020-9614 and CVE-2020-9613 and verify that the impacted system is patched.", + "false_positives": [ + "Trusted system or Adobe Acrobat Related processes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Child Process of Adobe Acrobat Reader Update Service", + "query": "event.category:process and event.type:(start or process_started) and process.parent.name:com.adobe.ARMDC.SMJobBlessHelper and user.name:root and not process.executable: (/Library/PrivilegedHelperTools/com.adobe.ARMDC.SMJobBlessHelper or /usr/bin/codesign or /private/var/folders/zz/*/T/download/ARMDCHammer or /usr/sbin/pkgutil or /usr/bin/shasum or /usr/bin/perl* or /usr/sbin/spctl or /usr/sbin/installer)", + "references": [ + "https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/" + ], + "risk_score": 73, + "rule_id": "f85ce03f-d8a8-4c83-acdc-5c8cd0592be7", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json new file mode 100644 index 00000000000000..813265d95cb745 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modification of the dynamic linker preload shared object (ld.so.preload). Adversaries may execute malicious payloads by hijacking the dynamic linker used to load libraries.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Dynamic Linker Preload Shared Object", + "query": "event.category:file and not event.type:deletion and file.path:/etc/ld.so.preload", + "references": [ + "https://www.anomali.com/blog/rocke-evolves-its-arsenal-with-a-new-malware-family-written-in-golang" + ], + "risk_score": 47, + "rule_id": "717f82c2-7741-4f9b-85b8-d06aeb853f4f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Privilege Escalation", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.006", + "name": "LD_PRELOAD", + "reference": "https://attack.mitre.org/techniques/T1574/006/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json new file mode 100644 index 00000000000000..f303dbc81e6b24 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to add an account to the admin group via the command line. This could be an indication of privilege escalation activity.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Admin Group Account Addition", + "query": "event.category:process and event.type:(start or process_started) and process.name:(dscl or dseditgroup) and process.args:((\"/Groups/admin\" or admin) and (\"-a\" or \"-append\"))", + "references": [ + "https://managingosx.wordpress.com/2010/01/14/add-a-user-to-the-admin-group-via-command-line-3-0/" + ], + "risk_score": 47, + "rule_id": "565c2b44-7a21-4818-955f-8d4737967d2e", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json new file mode 100644 index 00000000000000..10a5e3ba744c9e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json @@ -0,0 +1,75 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries can use the autostart mechanism provided by the Local Security Authority (LSA) authentication packages for privilege escalation or persistence by placing a reference to a binary in the Windows registry. The binary will then be executed by SYSTEM when the authentication packages are loaded.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential LSA Authentication Package Abuse", + "query": "registry where event.type == \"change\" and\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Lsa\\\\Authentication Packages\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "risk_score": 47, + "rule_id": "e9abe69b-1deb-4e19-ac4a-5d5ac00f72eb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json index d78c5ba7a3814d..16a8cdf64ad0c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies a privilege escalation attempt via named pipe impersonation. An adversary may abuse this technique by utilizing a framework such Metasploit's meterpreter getsystem command.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json new file mode 100644 index 00000000000000..81248b7e0be1e0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json @@ -0,0 +1,84 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the loading of a non Microsoft signed DLL that is missing on a default Windows install (phantom DLL) or one that can be loaded from a different location by a native Windows process. This may be abused to persist or elevate privileges via privileged file write vulnerabilities.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious DLL Loaded for Persistence or Privilege Escalation", + "query": "library where dll.name :\n (\n \"wlbsctrl.dll\",\n \"wbemcomn.dll\",\n \"WptsExtensions.dll\",\n \"Tsmsisrv.dll\",\n \"TSVIPSrv.dll\",\n \"Msfte.dll\",\n \"wow64log.dll\",\n \"WindowsCoreDeviceInfo.dll\",\n \"Ualapi.dll\",\n \"wlanhlp.dll\",\n \"phoneinfo.dll\",\n \"EdgeGdi.dll\",\n \"cdpsgshims.dll\",\n \"windowsperformancerecordercontrol.dll\",\n \"diagtrack_win.dll\"\n ) and \nnot (dll.code_signature.subject_name : \"Microsoft Windows\" and dll.code_signature.status : \"trusted\")\n", + "references": [ + "https://itm4n.github.io/windows-dll-hijacking-clarified/", + "http://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html", + "https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html", + "https://shellz.club/edgegdi-dll-for-persistence-and-lateral-movement/", + "https://windows-internals.com/faxing-your-way-to-system/", + "http://waleedassar.blogspot.com/2013/01/wow64logdll.html" + ], + "risk_score": 73, + "rule_id": "bfeaf89b-a2a7-48a3-817f-e41829dc61ee", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.002", + "name": "DLL Side-Loading", + "reference": "https://attack.mitre.org/techniques/T1574/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.001", + "name": "DLL Search Order Hijacking", + "reference": "https://attack.mitre.org/techniques/T1574/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json new file mode 100644 index 00000000000000..6d72fa84b5f246 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies port monitor and print processor registry modifications. Adversaries may abuse port monitor and print processors to run malicious DLLs during system boot that will be executed as SYSTEM for privilege escalation and/or persistence, if permissions allow writing a fully-qualified pathname for that DLL.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Port Monitor or Print Processor Registration Abuse", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Monitors\\\\*\",\n \"HLLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Environments\\\\Windows*\\\\Print Processors\\\\*\") and\n registry.data.strings : \"*.dll\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "references": [ + "https://www.welivesecurity.com/2020/05/21/no-game-over-winnti-group/" + ], + "risk_score": 47, + "rule_id": "8f3e91c7-d791-4704-80a1-42c160d7aa27", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.010", + "name": "Port Monitors", + "reference": "https://attack.mitre.org/techniques/T1547/010/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.010", + "name": "Port Monitors", + "reference": "https://attack.mitre.org/techniques/T1547/010/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json index 76b17b0d892294..7346e5f1e06db4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -17,7 +18,7 @@ "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Privilege%20Escalation/privesc_sysmon_cve_20201030_spooler.evtx", "https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1030" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "bd7eefee-f671-494e-98df-f01daf9e5f17", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json index 5a14984464bcb2..fae71bc2f98bbe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -16,7 +17,7 @@ "https://voidsec.com/cve-2020-1337-printdemon-is-dead-long-live-printdemon/", "https://www.thezdi.com/blog/2020/7/8/cve-2020-1300-remote-code-execution-through-microsoft-windows-cab-files" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "5bb4a95d-5a08-48eb-80db-4c3a63ec78a8", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json index 3fcaea3c039e40..c6b7bb9838045b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -16,7 +17,7 @@ "references": [ "https://safebreach.com/Post/How-we-bypassed-CVE-2020-1048-Patch-and-got-CVE-2020-1337" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "a7ccae7b-9d2c-44b2-a061-98e5946971fa", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json index 3885aa3d847ca4..a160466cdc4d61 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies a privilege escalation attempt via a rogue Windows directory (Windir) environment variable. This is a known primitive that is often combined with other vulnerabilities to elevate privileges.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -14,7 +16,7 @@ "references": [ "https://www.tiraniddo.dev/2017/05/exploiting-environment-variables-in.html" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "d563aaba-2e72-462b-8658-3e5ea22db3a6", "severity": "high", "tags": [ @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json new file mode 100644 index 00000000000000..4ca3c62012aded --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the root crontab file. Adversaries may overwrite this file to gain code execution with root privileges by exploiting privileged file write or move related vulnerabilities.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Privilege Escalation via Root Crontab File Modification", + "query": "event.category:file and not event.type:deletion and file.path:/private/var/at/tabs/root and not process.executable:/usr/bin/crontab", + "references": [ + "https://phoenhex.re/2017-06-09/pwn2own-diskarbitrationd-privesc", + "https://www.exploit-db.com/exploits/42146" + ], + "risk_score": 73, + "rule_id": "0ff84c42-873d-41a2-a4ed-08d74d352d01", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json deleted file mode 100644 index 0bed7960781927..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "An adversary may add the setgid bit to a file or directory in order to run a file with the privileges of the owning group. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setgid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", - "from": "now-9m", - "index": [ - "auditbeat-*", - "logs-endpoint.events.*" - ], - "language": "lucene", - "license": "Elastic License", - "max_signals": 33, - "name": "Setgid Bit Set via chmod", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:chmod AND process.args:(g+s OR /2[0-9]{3}/) AND NOT user.name:root", - "risk_score": 21, - "rule_id": "3a86e085-094c-412d-97ff-2439731e59cb", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Privilege Escalation" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1548", - "name": "Abuse Elevation Control Mechanism", - "reference": "https://attack.mitre.org/techniques/T1548/", - "subtechnique": [ - { - "id": "T1548.001", - "name": "Setuid and Setgid", - "reference": "https://attack.mitre.org/techniques/T1548/001/" - } - ] - } - ] - }, - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [] - } - ], - "timestamp_override": "event.ingested", - "type": "query", - "version": 6 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json similarity index 63% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json index a55f02a0af61aa..0501604f5c8405 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "An adversary may add the setuid bit to a file or directory in order to run a file with the privileges of the owning user. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setuid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", + "description": "An adversary may add the setuid or setgid bit to a file or directory in order to run a file with the privileges of the owning user or group. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setuid or setgid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", "from": "now-9m", "index": [ "auditbeat-*", @@ -11,8 +11,8 @@ "language": "lucene", "license": "Elastic License", "max_signals": 33, - "name": "Setuid Bit Set via chmod", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:chmod AND process.args:(u+s OR /4[0-9]{3}/) AND NOT user.name:root", + "name": "Setuid / Setgid Bit Set via chmod", + "query": "event.category:process AND event.type:(start OR process_started) AND process.name:chmod AND process.args:(\"+s\" OR \"u+s\" OR /4[0-9]{3}/ OR g+s OR /2[0-9]{3}/)", "risk_score": 21, "rule_id": "8a1b0278-0f9a-487d-96bd-d4833298e87a", "severity": "low", @@ -20,6 +20,7 @@ "Elastic", "Host", "Linux", + "macOS", "Threat Detection", "Privilege Escalation" ], @@ -58,5 +59,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json new file mode 100644 index 00000000000000..f014318c04eaba --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the attempted use of a heap-based buffer overflow vulnerability for the Sudo binary in Unix-like systems (CVE-2021-3156). Successful exploitation allows an unprivileged user to escalate to the root user.", + "false_positives": [ + "This rule could generate false positives if the process arguments leveraged by the exploit are shared by custom scripts using the Sudo or Sudoedit binaries. Only Sudo versions 1.8.2 through 1.8.31p2 and 1.9.0 through 1.9.5p1 are affected; if those versions are not present on the endpoint, this could be a false positive." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Sudo Heap-Based Buffer Overflow Attempt", + "query": "event.category:process and event.type:start and process.name:(sudo or sudoedit) and process.args:(*\\\\ and (\"-i\" or \"-s\"))", + "references": [ + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-3156", + "https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit", + "https://www.bleepingcomputer.com/news/security/latest-macos-big-sur-also-has-sudo-root-privilege-escalation-flaw", + "https://www.sudo.ws/alerts/unescape_overflow.html" + ], + "risk_score": 73, + "rule_id": "f37f3054-d40b-49ac-aa9b-a786c74c58b8", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + } + ] + } + ], + "threshold": { + "field": "host.hostname", + "value": 100 + }, + "type": "threshold", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json index a49d2d89527ac9..2ac74bff3a69f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json @@ -11,14 +11,15 @@ "language": "kuery", "license": "Elastic License", "name": "Sudoers File Modification", - "query": "event.category:file and event.type:change and file.path:/etc/sudoers", - "risk_score": 21, + "query": "event.category:file and event.type:change and file.path:(/etc/sudoers* or /private/etc/sudoers*)", + "risk_score": 47, "rule_id": "931e25a5-0f5e-4ae0-ba0d-9e94eff7e3a4", - "severity": "low", + "severity": "medium", "tags": [ "Elastic", "Host", "Linux", + "macOS", "Threat Detection", "Privilege Escalation" ], @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json index 6bef3776153dbb..e028665bbdc907 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json @@ -6,16 +6,17 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "UAC Bypass Attempt with IEditionUpgradeManager Elevated COM Interface", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"Clipup.exe\" and \nprocess.executable != \"C:\\\\Windows\\\\System32\\\\ClipUp.exe\" and process.parent.name == \"dllhost.exe\" and\n /* CLSID of the Elevated COM Interface IEditionUpgradeManager */\n wildcard(process.parent.args,\"/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41}\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"Clipup.exe\" and\n not process.executable : \"C:\\\\Windows\\\\System32\\\\ClipUp.exe\" and process.parent.name : \"dllhost.exe\" and\n /* CLSID of the Elevated COM Interface IEditionUpgradeManager */\n process.parent.args : \"/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41}\"\n", "references": [ "https://github.com/hfiref0x/UACME" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "b90cdde7-7e0d-4359-8bf0-2c112ce2008a", "severity": "high", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json index bcf916733d66b2..218322f4fdfa05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "UAC Bypass Attempt via Elevated COM Internet Explorer Add-On Installer", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n wildcard(process.executable, \"C:\\\\*\\\\AppData\\\\*\\\\Temp\\\\IDC*.tmp\\\\*.exe\") and\n process.parent.name == \"ieinstal.exe\" and process.parent.args == \"-Embedding\"\n\n /* uncomment once in winlogbeat */\n /* and not (process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true) */\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.executable : \"C:\\\\*\\\\AppData\\\\*\\\\Temp\\\\IDC*.tmp\\\\*.exe\" and\n process.parent.name : \"ieinstal.exe\" and process.parent.args : \"-Embedding\"\n\n /* uncomment once in winlogbeat */\n /* and not (process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true) */\n", "references": [ "https://swapcontext.blogspot.com/2020/11/uac-bypasses-from-comautoapprovallist.html" ], @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json index 81a796baa8824b..e9ba470cb2f35e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json index a81ff4214f5076..3dd864083b732b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json index 5b10fa84ea8695..bffbaad5136b42 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json index a73e6f4ccaa694..59f583e31dfd49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json index 76de6ef20f319e..3867fd918fae78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -15,7 +16,7 @@ "references": [ "https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "290aca65-e94d-403b-ba0f-62f320e63f51", "severity": "high", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json index 983886377cf371..040c921f198a3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json index 2cd74014708e9f..0854fe45cdf9d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json index deaa05dc5f8d46..58e2f1ca3d81f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -66,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6177fc4cd46614..977b38e59f8561 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -374,6 +374,100 @@ export const sampleSignalHit = (): SignalHit => ({ }, }); +export const sampleThresholdSignalHit = (): SignalHit => ({ + '@timestamp': '2020-04-20T21:27:45+0000', + event: { + kind: 'signal', + }, + signal: { + parents: [ + { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + { + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + { + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2021-02-16T17:37:34.275Z', + status: 'open', + threshold_result: { + count: 72, + terms: [{ field: 'host.name', value: 'a hostname' }], + cardinality: [{ field: 'process.name', value: 6 }], + }, + rule: { + author: [], + id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + created_at: '2020-04-20T21:27:45+0000', + updated_at: '2020-04-20T21:27:45+0000', + created_by: 'elastic', + description: 'some description', + enabled: true, + false_positives: ['false positive 1', 'false positive 2'], + from: 'now-6m', + immutable: false, + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + references: ['test 1', 'test 2'], + severity: 'high', + severity_mapping: [], + threshold: { + field: ['host.name'], + value: 5, + cardinality_field: 'process.name', + cardinality_value: 2, + }, + updated_by: 'elastic_kibana', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + threat: [], + version: 1, + status: 'succeeded', + status_date: '2020-02-22T16:47:50.047Z', + last_success_at: '2020-02-22T16:47:50.047Z', + last_success_message: 'succeeded', + output_index: '.siem-signals-default', + max_signals: 100, + risk_score: 55, + risk_score_mapping: [], + language: 'kuery', + rule_id: 'query-rule-id', + interval: '5m', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, +}); + +export const sampleWrappedThresholdSignalHit = (): WrappedSignalHit => { + return { + _index: 'myFakeSignalIndex', + _id: sampleIdGuid, + _source: sampleThresholdSignalHit(), + }; +}; + export const sampleBulkCreateDuplicateResult = { took: 60, errors: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index ca8fa821ce032c..362c368881b37f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -36,7 +36,13 @@ describe('buildBulkBody', () => { delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, - ruleParams: sampleParams, + ruleParams: { + ...sampleParams, + threshold: { + field: ['host.name'], + value: 100, + }, + }, id: sampleRuleGuid, name: 'rule-name', actions: [], @@ -110,6 +116,10 @@ describe('buildBulkBody', () => { severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], + threshold: { + field: ['host.name'], + value: 100, + }, throttle: 'no_actions', type: 'query', to: 'now', @@ -136,15 +146,25 @@ describe('buildBulkBody', () => { _source: { ...baseDoc._source, threshold_result: { + terms: [ + { + value: 'abcd', + }, + ], count: 5, - value: 'abcd', }, }, }; delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, - ruleParams: sampleParams, + ruleParams: { + ...sampleParams, + threshold: { + field: [], + value: 4, + }, + }, id: sampleRuleGuid, name: 'rule-name', actions: [], @@ -218,6 +238,10 @@ describe('buildBulkBody', () => { severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], + threshold: { + field: [], + value: 4, + }, throttle: 'no_actions', type: 'query', to: 'now', @@ -231,8 +255,12 @@ describe('buildBulkBody', () => { exceptions_list: getListArrayMock(), }, threshold_result: { + terms: [ + { + value: 'abcd', + }, + ], count: 5, - value: 'abcd', }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index cfbcfe5a04e59f..78ff0e8e1e5dd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { SearchTypes } from '../../../../common/detection_engine/types'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { SIGNALS_TEMPLATE_VERSION } from '../routes/index/get_signals_template'; import { isEventTypeSignal } from './build_event_type_signal'; -import { Signal, Ancestor, BaseSignalHit } from './types'; +import { Signal, Ancestor, BaseSignalHit, ThresholdResult } from './types'; /** * Takes a parent signal or event document and extracts the information needed for the corresponding entry in the child @@ -95,16 +96,24 @@ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => }; }; +const isThresholdResult = (thresholdResult: SearchTypes): thresholdResult is ThresholdResult => { + return typeof thresholdResult === 'object'; +}; + /** * Creates signal fields that are only available in the special case where a signal has only 1 parent signal/event. * @param doc The parent signal/event of the new signal to be built. */ export const additionalSignalFields = (doc: BaseSignalHit) => { + const thresholdResult = doc._source.threshold_result; + if (thresholdResult != null && !isThresholdResult(thresholdResult)) { + throw new Error(`threshold_result failed to validate: ${thresholdResult}`); + } return { parent: buildParent(removeClashes(doc)), original_time: doc._source['@timestamp'], // This field has already been replaced with timestampOverride, if provided. original_event: doc._source.event ?? undefined, - threshold_result: doc._source.threshold_result, + threshold_result: thresholdResult, original_signal: doc._source.signal != null && !isEventTypeSignal(doc) ? doc._source.signal : undefined, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts index 713178345361db..56d71048bb81b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts @@ -9,27 +9,41 @@ import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from './__mocks__/es_results'; import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals'; import { calculateThresholdSignalUuid } from './utils'; +import { Threshold } from '../../../../common/detection_engine/schemas/common/schemas'; describe('transformThresholdResultsToEcs', () => { it('should return transformed threshold results', () => { - const threshold = { - field: 'source.ip', + const threshold: Threshold = { + field: ['source.ip', 'host.name'], value: 1, + cardinality_field: 'destination.ip', + cardinality_value: 5, }; const startedAt = new Date('2020-12-17T16:27:00Z'); const transformedResults = transformThresholdResultsToEcs( { ...sampleDocSearchResultsNoSortId('abcd'), aggregations: { - threshold: { + 'threshold_0:source.ip': { buckets: [ { key: '127.0.0.1', - doc_count: 1, - top_threshold_hits: { - hits: { - hits: [sampleDocNoSortId('abcd')], - }, + doc_count: 15, + 'threshold_1:host.name': { + buckets: [ + { + key: 'garden-gnomes', + doc_count: 12, + top_threshold_hits: { + hits: { + hits: [sampleDocNoSortId('abcd')], + }, + }, + cardinality_count: { + value: 7, + }, + }, + ], }, }, ], @@ -44,7 +58,12 @@ describe('transformThresholdResultsToEcs', () => { '1234', undefined ); - const _id = calculateThresholdSignalUuid('1234', startedAt, 'source.ip', '127.0.0.1'); + const _id = calculateThresholdSignalUuid( + '1234', + startedAt, + ['source.ip', 'host.name'], + '127.0.0.1,garden-gnomes' + ); expect(transformedResults).toEqual({ took: 10, timed_out: false, @@ -67,10 +86,25 @@ describe('transformThresholdResultsToEcs', () => { _id, _index: 'test', _source: { - '@timestamp': ['2020-04-20T21:27:45+0000'], + '@timestamp': '2020-04-20T21:27:45+0000', threshold_result: { - count: 1, - value: '127.0.0.1', + terms: [ + { + field: 'source.ip', + value: '127.0.0.1', + }, + { + field: 'host.name', + value: 'garden-gnomes', + }, + ], + cardinality: [ + { + field: 'destination.ip', + value: 7, + }, + ], + count: 12, }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts index dd9e1e97a2b73a..29fd189bb34f34 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts @@ -18,12 +18,13 @@ import { AlertInstanceState, AlertServices, } from '../../../../../alerts/server'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; +import { BaseHit, RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; -import { SignalSearchResponse, ThresholdAggregationBucket } from './types'; -import { calculateThresholdSignalUuid } from './utils'; +import { calculateThresholdSignalUuid, getThresholdAggregationParts } from './utils'; import { BuildRuleMessage } from './rule_messages'; +import { TermAggregationBucket } from '../../types'; +import { MultiAggBucket, SignalSearchResponse, SignalSource } from './types'; interface BulkCreateThresholdSignalsParams { actions: RuleAlertAction[]; @@ -73,52 +74,150 @@ const getTransformedHits = ( logger.warn(`No hits returned, but totalResults >= threshold.value (${threshold.value})`); return []; } + const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields); + if (timestampArray == null) { + return []; + } + const timestamp = timestampArray[0]; + if (typeof timestamp !== 'string') { + return []; + } const source = { - '@timestamp': get(timestampOverride ?? '@timestamp', hit.fields), + '@timestamp': timestamp, threshold_result: { + terms: [ + { + value: ruleId, + }, + ], count: totalResults, - value: ruleId, }, }; return [ { _index: inputIndex, - _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field), + _id: calculateThresholdSignalUuid( + ruleId, + startedAt, + Array.isArray(threshold.field) ? threshold.field : [threshold.field] + ), _source: source, }, ]; } - if (!results.aggregations?.threshold) { + const aggParts = results.aggregations && getThresholdAggregationParts(results.aggregations); + if (!aggParts) { return []; } - return results.aggregations.threshold.buckets - .map( - ({ key, doc_count: docCount, top_threshold_hits: topHits }: ThresholdAggregationBucket) => { - const hit = topHits.hits.hits[0]; - if (hit == null) { - return null; + const getCombinations = (buckets: TermAggregationBucket[], i: number, field: string) => { + return buckets.reduce((acc: MultiAggBucket[], bucket: TermAggregationBucket) => { + if (i < threshold.field.length - 1) { + const nextLevelIdx = i + 1; + const nextLevelAggParts = getThresholdAggregationParts(bucket, nextLevelIdx); + if (nextLevelAggParts == null) { + throw new Error('Something went horribly wrong'); } - - const source = { - '@timestamp': get(timestampOverride ?? '@timestamp', hit.fields), - threshold_result: { - count: docCount, - value: key, - }, + const nextLevelPath = `['${nextLevelAggParts.name}']['buckets']`; + const nextBuckets = get(nextLevelPath, bucket); + const combinations = getCombinations(nextBuckets, nextLevelIdx, nextLevelAggParts.field); + combinations.forEach((val) => { + const el = { + terms: [ + { + field, + value: bucket.key, + }, + ...val.terms, + ], + cardinality: val.cardinality, + topThresholdHits: val.topThresholdHits, + docCount: val.docCount, + }; + acc.push(el); + }); + } else { + const el = { + terms: [ + { + field, + value: bucket.key, + }, + ], + cardinality: !isEmpty(threshold.cardinality_field) + ? [ + { + field: Array.isArray(threshold.cardinality_field) + ? threshold.cardinality_field[0] + : threshold.cardinality_field!, + value: bucket.cardinality_count!.value, + }, + ] + : undefined, + topThresholdHits: bucket.top_threshold_hits, + docCount: bucket.doc_count, }; + acc.push(el); + } - return { - _index: inputIndex, - _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field, key), - _source: source, - }; + return acc; + }, []); + }; + + return getCombinations(results.aggregations[aggParts.name].buckets, 0, aggParts.field).reduce( + (acc: Array>, bucket) => { + const hit = bucket.topThresholdHits?.hits.hits[0]; + if (hit == null) { + return acc; } - ) - .filter((bucket: ThresholdAggregationBucket) => bucket != null); + + const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields); + if (timestampArray == null) { + return acc; + } + + const timestamp = timestampArray[0]; + if (typeof timestamp !== 'string') { + return acc; + } + + const source = { + '@timestamp': timestamp, + threshold_result: { + terms: bucket.terms.map((term) => { + return { + field: term.field, + value: term.value, + }; + }), + cardinality: bucket.cardinality?.map((cardinality) => { + return { + field: cardinality.field, + value: cardinality.value, + }; + }), + count: bucket.docCount, + }, + }; + + acc.push({ + _index: inputIndex, + _id: calculateThresholdSignalUuid( + ruleId, + startedAt, + Array.isArray(threshold.field) ? threshold.field : [threshold.field], + bucket.terms.map((term) => term.value).join(',') + ), + _source: source, + }); + + return acc; + }, + [] + ); }; export const transformThresholdResultsToEcs = ( @@ -149,7 +248,7 @@ export const transformThresholdResultsToEcs = ( }, }; - delete thresholdResults.aggregations; // no longer needed + delete thresholdResults.aggregations; // delete because no longer needed set(thresholdResults, 'results.hits.total', transformedHits.length); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts index 6144f1f4b3823d..7796346e9876d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { set } from '@elastic/safer-lodash-set'; import { isEmpty } from 'lodash/fp'; import { @@ -49,38 +50,68 @@ export const findThresholdSignals = async ({ searchDuration: string; searchErrors: string[]; }> => { + const thresholdFields = Array.isArray(threshold.field) ? threshold.field : [threshold.field]; + const aggregations = threshold && !isEmpty(threshold.field) - ? { - threshold: { + ? thresholdFields.reduce((acc, field, i) => { + const aggPath = [...Array(i + 1).keys()] + .map((j) => { + return `['threshold_${j}:${thresholdFields[j]}']`; + }) + .join(`['aggs']`); + set(acc, aggPath, { terms: { - field: threshold.field, - min_doc_count: threshold.value, + field, + min_doc_count: threshold.value, // not needed on parent agg, but can help narrow down result set size: 10000, // max 10k buckets }, - aggs: { - // Get the most recent hit per bucket - top_threshold_hits: { - top_hits: { - sort: [ - { - [timestampOverride ?? '@timestamp']: { - order: 'desc', - }, + }); + if (i === threshold.field.length - 1) { + const topHitsAgg = { + top_hits: { + sort: [ + { + [timestampOverride ?? '@timestamp']: { + order: 'desc', }, - ], - fields: [ - { - field: '*', - include_unmapped: true, + }, + ], + fields: [ + { + field: '*', + include_unmapped: true, + }, + ], + size: 1, + }, + }; + // TODO: support case where threshold fields are not supplied, but cardinality is? + if (!isEmpty(threshold.cardinality_field)) { + set(acc, `${aggPath}['aggs']`, { + top_threshold_hits: topHitsAgg, + cardinality_count: { + cardinality: { + field: threshold.cardinality_field, + }, + }, + cardinality_check: { + bucket_selector: { + buckets_path: { + cardinalityCount: 'cardinality_count', }, - ], - size: 1, + script: `params.cardinalityCount >= ${threshold.cardinality_value}`, // TODO: cardinality operator + }, }, - }, - }, - }, - } + }); + } else { + set(acc, `${aggPath}['aggs']`, { + top_threshold_hits: topHitsAgg, + }); + } + } + return acc; + }, {}) : {}; return singleSearchAfter({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index da7ee8796afbfc..710a925fe315b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -40,7 +40,12 @@ export const signalSchema = schema.object({ severityMapping: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threat: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threshold: schema.maybe( - schema.object({ field: schema.nullable(schema.string()), value: schema.number() }) + schema.object({ + field: schema.nullable(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + value: schema.number(), + cardinality_field: schema.nullable(schema.string()), // TODO: depends on `field` being defined? + cardinality_value: schema.nullable(schema.number()), + }) ), timestampOverride: schema.nullable(schema.string()), to: schema.string(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index ecb36a8b050d98..98c9dd41d179c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -374,6 +374,10 @@ export const signalRulesAlertType = ({ } else if (isThresholdRule(type) && threshold) { const inputIndex = await getInputIndex(services, version, index); + const thresholdFields = Array.isArray(threshold.field) + ? threshold.field + : [threshold.field]; + const { filters: bucketFilters, searchErrors: previousSearchErrors, @@ -384,7 +388,7 @@ export const signalRulesAlertType = ({ services, logger, ruleId, - bucketByField: threshold.field, + bucketByFields: thresholdFields, timestampOverride, buildRuleMessage, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index e33ee4d5762ab8..15261ab5fad01b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts @@ -172,8 +172,10 @@ export const singleBulkCreate = async ({ logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`)); const createdItems = filteredEvents.hits.hits - .map((doc) => - buildBulkBody({ + .map((doc, index) => ({ + _id: response.items[index].create?._id ?? '', + _index: response.items[index].create?._index ?? '', + ...buildBulkBody({ doc, ruleParams, id, @@ -187,8 +189,8 @@ export const singleBulkCreate = async ({ enabled, tags, throttle, - }) - ) + }), + })) .filter((_, index) => get(response.items[index], 'create.status') === 201); const createdItemsCount = createdItems.length; const duplicateSignalsCount = countBy(response.items, 'create.status')['409']; @@ -263,7 +265,11 @@ export const bulkInsertSignals = async ( const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0; const createdItems = signals - .map((doc) => doc._source) + .map((doc, index) => ({ + ...doc._source, + _id: response.items[index].create?._id ?? '', + _index: response.items[index].create?._index ?? '', + })) .filter((_, index) => get(response.items[index], 'create.status') === 201); logger.debug(`bulk created ${createdItemsCount} signals`); return { bulkCreateDuration: makeFloatString(end - start), createdItems, createdItemsCount }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index fbea610428bd0f..9c58bba296ad35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -87,6 +87,34 @@ export const singleSearchAfter = async ({ }; } catch (exc) { logger.error(buildRuleMessage(`[-] nextSearchAfter threw an error ${exc}`)); + if ( + exc.message.includes('No mapping found for [@timestamp] in order to sort on') || + exc.message.includes(`No mapping found for [${timestampOverride}] in order to sort on`) + ) { + logger.error(buildRuleMessage(`[-] failure reason: ${exc.message}`)); + + const searchRes: SignalSearchResponse = { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + failed: 0, + skipped: 0, + }, + hits: { + total: 0, + max_score: 0, + hits: [], + }, + }; + return { + searchResult: searchRes, + searchDuration: '-1.0', + searchErrors: exc.message, + }; + } + throw exc; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts index 4f38f2db9230a9..cdbafe692c6305 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts @@ -5,13 +5,12 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import { SignalSearchResponse, SignalsEnrichment } from '../types'; import { enrichSignalThreatMatches } from './enrich_signal_threat_matches'; import { getThreatList } from './get_threat_list'; import { BuildThreatEnrichmentOptions, GetMatchedThreats } from './types'; -const DEFAULT_INDICATOR_PATH = 'threat.indicator'; - export const buildThreatEnrichment = ({ buildRuleMessage, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts index fada3141168711..f98f0c88a2dfae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts @@ -6,6 +6,7 @@ */ import { get } from 'lodash'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import { getThreatListItemMock } from './build_threat_mapping_filter.mock'; import { @@ -93,7 +94,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries: [], threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([]); @@ -103,7 +104,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.atomic')).toEqual('domain_1'); @@ -113,7 +114,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.field')).toEqual('event.field'); @@ -123,7 +124,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.type')).toEqual('type_1'); @@ -152,7 +153,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toHaveLength(queries.length); @@ -162,7 +163,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -227,7 +228,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -252,7 +253,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -284,7 +285,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -316,7 +317,7 @@ describe('buildMatchedIndicator', () => { buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }) ).toThrowError('Expected indicator field to be an object, but found: not an object'); }); @@ -337,7 +338,7 @@ describe('buildMatchedIndicator', () => { buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }) ).toThrowError('Expected indicator field to be an object, but found: not an object'); }); @@ -366,7 +367,7 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); expect(enrichedSignals.hits.hits).toEqual([]); @@ -381,10 +382,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { existing: 'indicator' }, @@ -406,10 +407,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { @@ -427,10 +428,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { existing: 'indicator' }, @@ -450,7 +451,7 @@ describe('enrichSignalThreatMatches', () => { }); const signals = getSignalsResponseMock([signalHit]); await expect(() => - enrichSignalThreatMatches(signals, getMatchedThreats, 'threat.indicator') + enrichSignalThreatMatches(signals, getMatchedThreats, DEFAULT_INDICATOR_PATH) ).rejects.toThrowError('Expected threat field to be an object, but found: whoops'); }); @@ -486,7 +487,7 @@ describe('enrichSignalThreatMatches', () => { 'custom_threat.custom_indicator' ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { @@ -529,13 +530,13 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); expect(enrichedSignals.hits.total).toEqual(expect.objectContaining({ value: 1 })); expect(enrichedSignals.hits.hits).toHaveLength(1); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts index c5b032207f1c5a..761a58224fac58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts @@ -6,6 +6,7 @@ */ import { get, isObject } from 'lodash'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import type { SignalSearchResponse, SignalSourceHit } from '../types'; import type { @@ -91,7 +92,7 @@ export const enrichSignalThreatMatches = async ( if (!isObject(threat)) { throw new Error(`Expected threat field to be an object, but found: ${threat}`); } - const existingIndicatorValue = get(signalHit._source, 'threat.indicator') ?? []; + const existingIndicatorValue = get(signalHit._source, DEFAULT_INDICATOR_PATH) ?? []; const existingIndicators = [existingIndicatorValue].flat(); // ensure indicators is an array return { @@ -105,14 +106,15 @@ export const enrichSignalThreatMatches = async ( }, }; }); - /* eslint-disable require-atomic-updates */ - signals.hits.hits = enrichedSignals; - if (isObject(signals.hits.total)) { - signals.hits.total.value = enrichedSignals.length; - } else { - signals.hits.total = enrichedSignals.length; - } - /* eslint-enable require-atomic-updates */ - return signals; + return { + ...signals, + hits: { + ...signals.hits, + hits: enrichedSignals, + total: isObject(signals.hits.total) + ? { ...signals.hits.total, value: enrichedSignals.length } + : enrichedSignals.length, + }, + }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts index dbad1d12d2be63..8ed5929e72504a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts @@ -24,7 +24,7 @@ interface FindPreviousThresholdSignalsParams { services: AlertServices; logger: Logger; ruleId: string; - bucketByField: string; + bucketByFields: string[]; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; } @@ -36,7 +36,7 @@ export const findPreviousThresholdSignals = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }: FindPreviousThresholdSignalsParams): Promise<{ @@ -44,22 +44,6 @@ export const findPreviousThresholdSignals = async ({ searchDuration: string; searchErrors: string[]; }> => { - const aggregations = { - threshold: { - terms: { - field: 'signal.threshold_result.value', - size: 10000, - }, - aggs: { - lastSignalTimestamp: { - max: { - field: 'signal.original_time', // timestamp of last event captured by bucket - }, - }, - }, - }, - }; - const filter = { bool: { must: [ @@ -68,17 +52,18 @@ export const findPreviousThresholdSignals = async ({ 'signal.rule.rule_id': ruleId, }, }, - { - term: { - 'signal.rule.threshold.field': bucketByField, - }, - }, + ...bucketByFields.map((field) => { + return { + term: { + 'signal.rule.threshold.field': field, + }, + }; + }), ], }, }; return singleSearchAfter({ - aggregations, searchAfterSortId: undefined, timestampOverride, index: indexPattern, @@ -87,7 +72,7 @@ export const findPreviousThresholdSignals = async ({ services, logger, filter, - pageSize: 0, + pageSize: 10000, // TODO: multiple pages? buildRuleMessage, excludeDocsWithTimestampOverride: false, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts new file mode 100644 index 00000000000000..ed9aa9a5ba698a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts @@ -0,0 +1,85 @@ +/* + * 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 { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; +import { mockLogger, sampleWrappedThresholdSignalHit } from './__mocks__/es_results'; +import { getThresholdBucketFilters } from './threshold_get_bucket_filters'; +import { buildRuleMessageFactory } from './rule_messages'; + +const buildRuleMessage = buildRuleMessageFactory({ + id: 'fake id', + ruleId: 'fake rule id', + index: 'fakeindex', + name: 'fake name', +}); + +describe('thresholdGetBucketFilters', () => { + let mockService: AlertServicesMock; + + beforeEach(() => { + jest.clearAllMocks(); + mockService = alertsMock.createAlertServices(); + }); + + it('should generate filters for threshold signal detection with dupe mitigation', async () => { + mockService.callCluster.mockResolvedValue({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total: 1, + max_score: 100, + hits: [sampleWrappedThresholdSignalHit()], + }, + }); + const result = await getThresholdBucketFilters({ + from: 'now-6m', + to: 'now', + indexPattern: ['*'], + services: mockService, + logger: mockLogger, + ruleId: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + bucketByFields: ['host.name'], + timestampOverride: undefined, + buildRuleMessage, + }); + expect(result).toEqual({ + filters: [ + { + bool: { + must_not: [ + { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-02-16T17:37:34.275Z', + }, + }, + }, + { + term: { + 'host.name': 'a hostname', + }, + }, + ], + }, + }, + ], + }, + }, + ], + searchErrors: [], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts index 96224be181f47c..e1727c0361afc8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts @@ -5,11 +5,13 @@ * 2.0. */ +import crypto from 'crypto'; import { isEmpty } from 'lodash'; import { Filter } from 'src/plugins/data/common'; import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertInstanceContext, @@ -17,7 +19,7 @@ import { AlertServices, } from '../../../../../alerts/server'; import { Logger } from '../../../../../../../src/core/server'; -import { ThresholdQueryBucket } from './types'; +import { ThresholdSignalHistory, ThresholdSignalHistoryRecord } from './types'; import { BuildRuleMessage } from './rule_messages'; import { findPreviousThresholdSignals } from './threshold_find_previous_signals'; @@ -28,7 +30,7 @@ interface GetThresholdBucketFiltersParams { services: AlertServices; logger: Logger; ruleId: string; - bucketByField: string; + bucketByFields: string[]; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; } @@ -40,7 +42,7 @@ export const getThresholdBucketFilters = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }: GetThresholdBucketFiltersParams): Promise<{ @@ -54,20 +56,85 @@ export const getThresholdBucketFilters = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }); - const filters = searchResult.aggregations.threshold.buckets.reduce( - (acc: ESFilter[], bucket: ThresholdQueryBucket): ESFilter[] => { + const thresholdSignalHistory = searchResult.hits.hits.reduce( + (acc, hit) => { + if (!hit._source) { + return acc; + } + + const terms = bucketByFields.map((field) => { + let signalTerms = hit._source.signal?.threshold_result?.terms; + + // Handle pre-7.12 signals + if (signalTerms == null) { + signalTerms = [ + { + field: (((hit._source.rule as RulesSchema).threshold as unknown) as { field: string }) + .field, + value: ((hit._source.signal?.threshold_result as unknown) as { value: string }).value, + }, + ]; + } else if (isEmpty(signalTerms)) { + signalTerms = []; + } + + const result = signalTerms.filter((resultField) => { + return resultField.field === field; + }); + + return { + field, + value: result[0].value, + }; + }); + + const hash = crypto + .createHash('sha256') + .update( + terms + .sort((term1, term2) => (term1.field > term2.field ? 1 : -1)) + .map((field) => { + return field.value; + }) + .join(',') + ) + .digest('hex'); + + const existing = acc[hash]; + const originalTime = + hit._source.signal?.original_time != null + ? new Date(hit._source.signal?.original_time).getTime() + : undefined; + + if (existing != null) { + if (originalTime && originalTime > existing.lastSignalTimestamp) { + acc[hash].lastSignalTimestamp = originalTime; + } + } else if (originalTime) { + acc[hash] = { + terms, + lastSignalTimestamp: originalTime, + }; + } + return acc; + }, + {} + ); + + const filters = Object.values(thresholdSignalHistory).reduce( + (acc: ESFilter[], bucket: ThresholdSignalHistoryRecord): ESFilter[] => { const filter = { bool: { filter: [ { range: { [timestampOverride ?? '@timestamp']: { - lte: bucket.lastSignalTimestamp.value_as_string, + lte: new Date(bucket.lastSignalTimestamp).toISOString(), }, }, }, @@ -75,11 +142,15 @@ export const getThresholdBucketFilters = async ({ }, } as ESFilter; - if (!isEmpty(bucketByField)) { - (filter.bool.filter as ESFilter[]).push({ - term: { - [bucketByField]: bucket.key, - }, + if (!isEmpty(bucketByFields)) { + bucket.terms.forEach((term) => { + if (term.field != null) { + (filter.bool.filter as ESFilter[]).push({ + term: { + [term.field]: `${term.value}`, + }, + }); + } }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index f7ac0425b2f2e4..e5ca1f6a60456a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -17,7 +17,7 @@ import { AlertExecutorOptions, AlertServices, } from '../../../../../alerts/server'; -import { BaseSearchResponse, SearchResponse, TermAggregationBucket } from '../../types'; +import { BaseSearchResponse, SearchHit, SearchResponse, TermAggregationBucket } from '../../types'; import { EqlSearchResponse, BaseHit, @@ -50,8 +50,27 @@ export interface SignalsStatusParams { } export interface ThresholdResult { + terms?: Array<{ + field?: string; + value: string; + }>; + cardinality?: Array<{ + field: string; + value: number; + }>; count: number; - value: string; +} + +export interface ThresholdSignalHistoryRecord { + terms: Array<{ + field?: string; + value: SearchTypes; + }>; + lastSignalTimestamp: number; +} + +export interface ThresholdSignalHistory { + [hash: string]: ThresholdSignalHistoryRecord; } export interface SignalSource { @@ -74,8 +93,9 @@ export interface SignalSource { }; // signal.depth doesn't exist on pre-7.10 signals depth?: number; + original_time?: string; + threshold_result?: ThresholdResult; }; - threshold_result?: ThresholdResult; } export interface BulkItem { @@ -276,6 +296,28 @@ export interface SearchAfterAndBulkCreateReturnType { export interface ThresholdAggregationBucket extends TermAggregationBucket { top_threshold_hits: BaseSearchResponse; + cardinality_count: { + value: number; + }; +} + +export interface MultiAggBucket { + cardinality?: Array<{ + field: string; + value: number; + }>; + terms: Array<{ + field: string; + value: string; + }>; + docCount: number; + topThresholdHits?: + | { + hits: { + hits: SearchHit[]; + }; + } + | undefined; } export interface ThresholdQueryBucket extends TermAggregationBucket { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index f7e1eb7622779c..7888bb6deaab79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -1445,13 +1445,13 @@ describe('utils', () => { describe('calculateThresholdSignalUuid', () => { it('should generate a uuid without key', () => { const startedAt = new Date('2020-12-17T16:27:00Z'); - const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'agent.name'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, ['agent.name']); expect(signalUuid).toEqual('a4832768-a379-583a-b1a2-e2ce2ad9e6e9'); }); it('should generate a uuid with key', () => { const startedAt = new Date('2019-11-18T13:32:00Z'); - const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'host.ip', '1.2.3.4'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, ['host.ip'], '1.2.3.4'); expect(signalUuid).toEqual('ee8870dc-45ff-5e6c-a2f9-80886651ce03'); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index f6bd5c8a325be1..58bf22be97bf87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -134,9 +134,10 @@ export const hasTimestampFields = async ( ? 'timestamp field "@timestamp"' : `timestamp override field "${timestampField}"` }: ${JSON.stringify( - isEmpty(timestampFieldCapsResponse.body.fields) + isEmpty(timestampFieldCapsResponse.body.fields) || + isEmpty(timestampFieldCapsResponse.body.fields[timestampField]) ? timestampFieldCapsResponse.body.indices - : timestampFieldCapsResponse.body.fields[timestampField].unmapped.indices + : timestampFieldCapsResponse.body.fields[timestampField]?.unmapped?.indices )}`; logger.error(buildRuleMessage(errorString)); await ruleStatusService.warning(errorString); @@ -698,9 +699,12 @@ export const createSearchAfterReturnTypeFromResponse = ({ searchResult._shards.failed === 0 || searchResult._shards.failures?.every((failure) => { return ( - failure.reason?.reason === 'No mapping found for [@timestamp] in order to sort on' || - failure.reason?.reason === + failure.reason?.reason?.includes( + 'No mapping found for [@timestamp] in order to sort on' + ) || + failure.reason?.reason?.includes( `No mapping found for [${timestampOverride}] in order to sort on` + ) ); }), lastLookBackDate: lastValidDate({ searchResult, timestampOverride }), @@ -851,7 +855,7 @@ export const createTotalHitsFromSearchResult = ({ export const calculateThresholdSignalUuid = ( ruleId: string, startedAt: Date, - thresholdField: string, + thresholdFields: string[], key?: string ): string => { // used to generate constant Threshold Signals ID when run with the same params @@ -859,7 +863,31 @@ export const calculateThresholdSignalUuid = ( const startedAtString = startedAt.toISOString(); const keyString = key ?? ''; - const baseString = `${ruleId}${startedAtString}${thresholdField}${keyString}`; + const baseString = `${ruleId}${startedAtString}${thresholdFields.join(',')}${keyString}`; return uuidv5(baseString, NAMESPACE_ID); }; + +export const getThresholdAggregationParts = ( + data: object, + index?: number +): + | { + field: string; + index: number; + name: string; + } + | undefined => { + const idx = index != null ? index.toString() : '\\d'; + const pattern = `threshold_(?${idx}):(?.*)`; + for (const key of Object.keys(data)) { + const matches = key.match(pattern); + if (matches != null && matches.groups?.name != null && matches.groups?.index != null) { + return { + field: matches.groups.name, + index: parseInt(matches.groups.index, 10), + name: key, + }; + } + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index 9d030e0d59bc61..a8616dc1c57d1a 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -75,8 +75,8 @@ export interface SearchHits { max_score: number; hits: Array< BaseHit & { - _type: string; - _score: number; + _type?: string; + _score?: number; _version?: number; _explanation?: Explanation; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -107,6 +107,14 @@ export type SearchHit = SearchResponse['hits']['hits'][0]; export interface TermAggregationBucket { key: string; doc_count: number; + top_threshold_hits?: { + hits: { + hits: SearchHit[]; + }; + }; + cardinality_count?: { + value: number; + }; } export interface TermAggregation { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 5f70b6cdc641e2..b675920977df15 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -198,9 +198,9 @@ export class Plugin implements IPlugin { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstLastSeen]: firstLastSeenHost, + [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 9388807f25a6fb..5cee547a6b365f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -15,7 +15,7 @@ import { SecuritySolutionFactory } from '../types'; import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; -import { firstLastSeenHost } from './last_first_seen'; +import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; import { hostsKpiAuthentications } from './kpi/authentications'; @@ -29,7 +29,7 @@ export const hostsFactory: Record< [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstLastSeen]: firstLastSeenHost, + [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts index 0cad31bffb2a19..a6d5dcdf022b59 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts @@ -5,9 +5,12 @@ * 2.0. */ -import { HostsQueries } from '../../../../../../../common/search_strategy'; +import { + HostFirstLastSeenRequestOptions, + HostsQueries, +} from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: HostFirstLastSeenRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'auditbeat-*', @@ -18,38 +21,164 @@ export const mockOptions = { 'winlogbeat-*', ], docValueFields: [], - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, hostName: 'siem-kibana', + order: 'asc', }; -export const mockSearchStrategyResponse = { +export const mockSearchStrategyFirstSeenResponse = { isPartial: false, isRunning: false, rawResponse: { took: 230, timed_out: false, _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, - hits: { total: -1, max_score: 0, hits: [] }, - aggregations: { - lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, - firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _type: 'doc', + _score: 0, + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], + }, + firstSeen: '2020-09-08T08:48:51.759Z', + }, + total: 21, + loaded: 21, +}; + +export const mockSearchStrategyLastSeenResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _type: 'doc', + _score: 0, + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], + }, + lastSeen: '2020-09-08T08:48:51.759Z', + }, + total: 21, + loaded: 21, +}; + +export const formattedSearchStrategyFirstResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _score: 0, + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], }, }, total: 21, loaded: 21, + inspect: { + dsl: [ + JSON.stringify( + { + allowNoIndices: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + ignoreUnavailable: true, + track_total_hits: false, + body: { + query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order: 'asc', + }, + }, + ], + }, + }, + null, + 2 + ), + ], + }, + firstSeen: '2021-02-18T02:37:37.682Z', }; -export const formattedSearchStrategyResponse = { +export const formattedSearchStrategyLastResponse = { isPartial: false, isRunning: false, rawResponse: { took: 230, timed_out: false, _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, - hits: { total: -1, max_score: 0, hits: [] }, - aggregations: { - lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, - firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _score: 0, + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], }, }, total: 21, @@ -71,12 +200,16 @@ export const formattedSearchStrategyResponse = { ignoreUnavailable: true, track_total_hits: false, body: { - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, - size: 0, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], }, }, null, @@ -84,8 +217,7 @@ export const formattedSearchStrategyResponse = { ), ], }, - firstSeen: '2020-06-08T10:22:02.000Z', - lastSeen: '2020-09-08T08:48:51.759Z', + lastSeen: '2021-02-18T02:37:37.682Z', }; export const expectedDsl = { @@ -102,11 +234,9 @@ export const expectedDsl = { ignoreUnavailable: true, track_total_hits: false, body: { - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, + _source: ['@timestamp'], query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, - size: 0, + size: 1, + sort: [{ '@timestamp': { order: 'asc' } }], }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts index d416586f59cf7a..d0405d829b83db 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts @@ -5,32 +5,66 @@ * 2.0. */ -import * as buildQuery from './query.last_first_seen_host.dsl'; -import { firstLastSeenHost } from '.'; +import * as buildQuery from './query.first_or_last_seen_host.dsl'; +import { firstOrLastSeenHost } from '.'; import { mockOptions, - mockSearchStrategyResponse, - formattedSearchStrategyResponse, + mockSearchStrategyFirstSeenResponse, + mockSearchStrategyLastSeenResponse, + formattedSearchStrategyLastResponse, + formattedSearchStrategyFirstResponse, } from './__mocks__'; +import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy'; describe('firstLastSeenHost search strategy', () => { - const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstLastSeenHostQuery'); + describe('first seen search strategy', () => { + const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); - afterEach(() => { - buildFirstLastSeenHostQuery.mockClear(); - }); + afterEach(() => { + buildFirstLastSeenHostQuery.mockClear(); + }); - describe('buildDsl', () => { - test('should build dsl query', () => { - firstLastSeenHost.buildDsl(mockOptions); - expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + describe('buildDsl', () => { + test('should build dsl query', () => { + firstOrLastSeenHost.buildDsl(mockOptions); + expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await firstOrLastSeenHost.parse( + mockOptions, + mockSearchStrategyFirstSeenResponse + ); + expect(result).toMatchObject(formattedSearchStrategyFirstResponse); + }); }); }); - describe('parse', () => { - test('should parse data correctly', async () => { - const result = await firstLastSeenHost.parse(mockOptions, mockSearchStrategyResponse); - expect(result).toMatchObject(formattedSearchStrategyResponse); + describe('last seen search strategy', () => { + const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); + + afterEach(() => { + buildFirstLastSeenHostQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + const options: HostFirstLastSeenRequestOptions = { ...mockOptions, order: 'desc' }; + firstOrLastSeenHost.buildDsl(options); + expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(options); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await firstOrLastSeenHost.parse( + { ...mockOptions, order: 'desc' }, + mockSearchStrategyLastSeenResponse + ); + expect(result).toMatchObject(formattedSearchStrategyLastResponse); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts index fd0c92ee2bdf57..fee9a49e42c483 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts @@ -5,12 +5,10 @@ * 2.0. */ -import { get } from 'lodash/fp'; +import { getOr } from 'lodash/fp'; import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; import { - HostAggEsData, - HostAggEsItem, HostFirstLastSeenStrategyResponse, HostsQueries, HostFirstLastSeenRequestOptions, @@ -18,24 +16,40 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; -import { buildFirstLastSeenHostQuery } from './query.last_first_seen_host.dsl'; +import { buildFirstOrLastSeenHostQuery } from './query.first_or_last_seen_host.dsl'; -export const firstLastSeenHost: SecuritySolutionFactory = { - buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstLastSeenHostQuery(options), +export const firstOrLastSeenHost: SecuritySolutionFactory = { + buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstOrLastSeenHostQuery(options), parse: async ( options: HostFirstLastSeenRequestOptions, - response: IEsSearchResponse + response: IEsSearchResponse ): Promise => { - const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + // First try to get the formatted field if it exists or not. + const formattedField: string | null = getOr( + null, + 'hits.hits[0].fields.@timestamp[0]', + response.rawResponse + ); + // If it doesn't exist, fall back on _source as a last try. + const seen: string | null = + formattedField || getOr(null, 'hits.hits[0]._source.@timestamp', response.rawResponse); + const inspect = { - dsl: [inspectStringifyObject(buildFirstLastSeenHostQuery(options))], + dsl: [inspectStringifyObject(buildFirstOrLastSeenHostQuery(options))], }; - return { - ...response, - inspect, - firstSeen: get('firstSeen.value_as_string', aggregations), - lastSeen: get('lastSeen.value_as_string', aggregations), - }; + if (options.order === 'asc') { + return { + ...response, + inspect, + firstSeen: seen, + }; + } else { + return { + ...response, + inspect, + lastSeen: seen, + }; + } }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts similarity index 82% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts index be9b24308f1f6f..fe2cb96144e22e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { buildFirstLastSeenHostQuery as buildQuery } from './query.last_first_seen_host.dsl'; +import { buildFirstOrLastSeenHostQuery as buildQuery } from './query.first_or_last_seen_host.dsl'; import { mockOptions, expectedDsl } from './__mocks__'; describe('buildQuery', () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts similarity index 69% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts index d601a5905dd6e2..21876b9aad11be 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts @@ -6,14 +6,14 @@ */ import { isEmpty } from 'lodash/fp'; -import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; -export const buildFirstLastSeenHostQuery = ({ +export const buildFirstOrLastSeenHostQuery = ({ hostName, defaultIndex, docValueFields, -}: HostFirstLastSeenRequestOptions): ISearchRequestParams => { + order, +}: HostFirstLastSeenRequestOptions) => { const filter = [{ term: { 'host.name': hostName } }]; const dslQuery = { @@ -23,12 +23,16 @@ export const buildFirstLastSeenHostQuery = ({ track_total_hits: false, body: { ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, query: { bool: { filter } }, - size: 0, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order, + }, + }, + ], }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts index 5ed324496e609e..15d0e2d5494b8c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts @@ -24,6 +24,7 @@ export const TIMELINE_EVENTS_FIELDS = [ 'signal.rule.version', 'signal.rule.severity', 'signal.rule.risk_score', + 'signal.threshold_result', 'event.code', 'event.module', 'event.action', diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts index 5c8f7f87a6c493..10bb606dc2387e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts @@ -320,6 +320,7 @@ describe('#formatTimelineData', () => { signal: { original_time: ['2021-01-09T13:39:32.595Z'], status: ['open'], + threshold_result: ['{"count":10000,"value":"2a990c11-f61b-4c8e-b210-da2574e9f9db"}'], rule: { building_block_type: [], exceptions_list: [], diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts index ab37c730c604d4..3b02e5621ed1ad 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts @@ -28,9 +28,19 @@ export const timelineEventsLastEventTime: SecuritySolutionTimelineFactory { jest.fn().mockRejectedValueOnce(new Error()), // Call to 'sr.policies' ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -140,8 +139,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -174,8 +172,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -286,8 +283,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [{}, jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -315,8 +311,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -372,8 +367,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -398,8 +392,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts index 3c656194aa8542..fa127880fd806a 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts @@ -51,7 +51,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -92,7 +92,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -131,7 +131,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -167,7 +167,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -222,7 +222,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -263,7 +263,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -323,7 +323,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts index 42c64ff3874feb..35dce2c5d558fa 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts @@ -108,8 +108,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error()), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -214,8 +213,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error()), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -292,8 +290,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error('Error getting pluggins')), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError('Error getting pluggins'); }); }); @@ -328,9 +325,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { const error = new Error('Oh no!'); router.callAsCurrentUserResponses = [{}, jest.fn().mockRejectedValueOnce(error)]; - const response = await router.runRequest(mockRequest); - expect(response.body.message).toEqual(error.message); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); }); }); @@ -358,8 +353,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts index f8a5f01e4cf3df..c9945bb172e6c3 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -66,7 +66,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } // If a managed repository, we also need to check if a policy is associated to it @@ -121,7 +121,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } const { @@ -203,7 +203,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -244,7 +244,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -285,7 +285,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -329,7 +329,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -371,7 +371,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -412,7 +412,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts index bbf90841e774eb..fe33331522daa8 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts @@ -48,8 +48,7 @@ describe('[Snapshot and Restore API Routes] Restore', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -111,8 +110,7 @@ describe('[Snapshot and Restore API Routes] Restore', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts b/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts index df2bc20b026c9b..c4300bafc75fbb 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts @@ -87,7 +87,7 @@ export function registerRestoreRoutes({ router, license, lib: { isEsError } }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -124,7 +124,7 @@ export function registerRestoreRoutes({ router, license, lib: { isEsError } }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index bcdd27a1f100c3..97eb34b4aaa732 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -144,8 +144,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { jest.fn().mockRejectedValueOnce(new Error('Error getting repository')), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -221,8 +220,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { mockSnapshotGetEsResponse, ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 65e941db88df6a..03e3b4ecc08870 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -61,7 +61,7 @@ export function registerSnapshotsRoutes({ body: e, }); } - return res.internalError({ body: e }); + throw e; } const snapshots: SnapshotDetails[] = []; @@ -176,7 +176,7 @@ export function registerSnapshotsRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -229,7 +229,7 @@ export function registerSnapshotsRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index dd2a60bedcac6a..dd7ed69aaf27f9 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -114,7 +114,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); await sleep(0); @@ -214,7 +214,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); await sleep(2000); @@ -282,7 +282,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); expect(await handler(context, req, res)).toMatchObject({ body: { diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts index 3d8d7ef4d8ae3a..d86df3af3e1d0a 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts @@ -76,12 +76,14 @@ export const runtimeMappingsSchema = schema.maybe( schema.literal('ip'), schema.literal('boolean'), ]), - script: schema.oneOf([ - schema.string(), - schema.object({ - source: schema.string(), - }), - ]), + script: schema.maybe( + schema.oneOf([ + schema.string(), + schema.object({ + source: schema.string(), + }), + ]) + ), }) ) ); diff --git a/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts b/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts index ad11f092e8e578..ded14a2c0e69e2 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts @@ -11,6 +11,7 @@ export const useDocumentationLinks = () => { const deps = useAppDependencies(); const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = deps.docLinks; return { + esAggsCompositeMissingBucket: deps.docLinks.links.aggs.composite_missing_bucket, esIndicesCreateIndex: deps.docLinks.links.apis.createIndex, esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`, esQueryDsl: deps.docLinks.links.query.queryDsl, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap index ba53bb2da57160..9c9fb59eea4b1d 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap @@ -1,93 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Transform: Group By Minimal initialization 1`] = ` - - - - - - - - - - Apply - - - + onChange={[Function]} + options={Object {}} + otherAggNames={Array []} +/> `; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx index 036342ef16344b..baf0bbd655925c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx @@ -8,6 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; + +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../../../src/plugins/data/public/mocks'; +const startMock = coreMock.createStart(); + import { AggName } from '../../../../../../common/types/aggregations'; import { PIVOT_SUPPORTED_GROUP_BY_AGGS, PivotGroupByConfig } from '../../../../common'; @@ -88,15 +94,24 @@ describe('Transform: Group By ', () => { const otherAggNames: AggName[] = []; const onChange = (item: PivotGroupByConfig) => {}; + // mock services for QueryStringInput + const services = { + ...startMock, + data: dataPluginMock.createStartContract(), + appName: 'the-test-app', + }; + const wrapper = shallow( - + + + ); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find(PopoverForm)).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx index 34a463c0f71b8c..da74553aa89c2a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { Fragment, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { + htmlIdGenerator, EuiButton, + EuiCheckbox, EuiCodeEditor, EuiFieldText, EuiForm, EuiFormRow, + EuiLink, EuiSelect, EuiSpacer, } from '@elastic/eui'; @@ -22,6 +25,8 @@ import { import { AggName } from '../../../../../../common/types/aggregations'; import { dictionaryToArray } from '../../../../../../common/types/common'; +import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; + import { dateHistogramIntervalFormatRegex, getEsAggFromGroupByConfig, @@ -94,6 +99,8 @@ interface Props { } export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onChange, options }) => { + const { esAggsCompositeMissingBucket } = useDocumentationLinks(); + const isUnsupportedAgg = !isPivotGroupByConfigWithUiSupport(defaultData); const [agg, setAgg] = useState(defaultData.agg); @@ -102,9 +109,14 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha isPivotGroupByConfigWithUiSupport(defaultData) ? defaultData.field : '' ); const [interval, setInterval] = useState(getDefaultInterval(defaultData)); + const [missingBucket, setMissingBucket] = useState( + isPivotGroupByConfigWithUiSupport(defaultData) && defaultData.missing_bucket + ); + + const missingBucketSwitchId = useMemo(() => htmlIdGenerator()(), []); function getUpdatedItem(): PivotGroupByConfig { - const updatedItem = { ...defaultData, agg, aggName, field }; + const updatedItem = { ...defaultData, agg, aggName, field, missing_bucket: missingBucket }; if (isGroupByHistogram(updatedItem) && interval !== undefined) { updatedItem.interval = interval; @@ -225,7 +237,7 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha defaultMessage: 'Interval', })} > - + <> {isGroupByHistogram(defaultData) && ( = ({ defaultData, otherAggNames, onCha onChange={(e) => setInterval(e.target.value)} /> )} - + + + )} + {!isUnsupportedAgg && ( + + {i18n.translate('xpack.transform.groupBy.popoverForm.missingBucketCheckboxHelpText', { + defaultMessage: 'Select to include documents without a value.', + })} +
+ + {i18n.translate( + 'xpack.transform.stepDetailsForm.missingBucketCheckboxHelpTextLink', + { + defaultMessage: `Learn more`, + } + )} + + + } + > + setMissingBucket(!missingBucket)} + />
)} {isUnsupportedAgg && ( - + <> = ({ defaultData, otherAggNames, onCha isReadOnly aria-label="Read only code editor" /> - + )} onChange(getUpdatedItem())}> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts index cdba7a3f5482c9..6c9293a6d13cf6 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -40,7 +40,7 @@ type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; export interface RuntimeField { type: RuntimeType; - script: + script?: | string | { source: string; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e53e166385ff52..df15a805abaf39 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1479,7 +1479,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", "discover.documentsAriaLabel": "ドキュメント", - "discover.docViews.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", "discover.docViews.json.jsonTitle": "JSON", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされません", @@ -5245,7 +5244,6 @@ "xpack.apm.serviceOverview.errorsTableTitle": "エラー", "xpack.apm.serviceOverview.instancesTableColumnCpuUsage": "CPU使用状況(平均)", "xpack.apm.serviceOverview.instancesTableColumnErrorRate": "エラー率", - "xpack.apm.serviceOverview.instancesTableColumnLatency": "レイテンシ", "xpack.apm.serviceOverview.instancesTableColumnMemoryUsage": "メモリー使用状況(平均)", "xpack.apm.serviceOverview.instancesTableColumnNodeName": "ノード名", "xpack.apm.serviceOverview.instancesTableColumnThroughput": "トラフィック", @@ -5258,9 +5256,6 @@ "xpack.apm.serviceOverview.throughtputChartTitle": "トラフィック", "xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "エラー率", "xpack.apm.serviceOverview.transactionsTableColumnImpact": "インパクト", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.avg": "レイテンシ(平均)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p95": "レイテンシ(95 番目)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p99": "レイテンシ(99 番目)", "xpack.apm.serviceOverview.transactionsTableColumnName": "名前", "xpack.apm.serviceOverview.transactionsTableLinkText": "トランザクションを表示", "xpack.apm.serviceOverview.transactionsTableTitle": "トランザクション", @@ -17239,7 +17234,6 @@ "xpack.securitySolution.case.caseView.reporterLabel": "報告者", "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "{ externalService }インシデントの更新が必要です", "xpack.securitySolution.case.caseView.sendEmalLinkAria": "クリックすると、{user}に電子メールを送信します", - "xpack.securitySolution.case.caseView.showAlertDeletedTooltip": "アラートが見つかりません", "xpack.securitySolution.case.caseView.showAlertTooltip": "アラートの詳細を表示", "xpack.securitySolution.case.caseView.statusLabel": "ステータス", "xpack.securitySolution.case.caseView.tags": "タグ", @@ -18864,8 +18858,6 @@ "xpack.securitySolution.hooks.useAddToTimeline.addedFieldMessage": "{fieldOrValue}をタイムラインに追加しました", "xpack.securitySolution.host.details.architectureLabel": "アーキテクチャー", "xpack.securitySolution.host.details.endpoint.endpointPolicy": "統合", - "xpack.securitySolution.host.details.endpoint.policyStatus": "構成ステータス", - "xpack.securitySolution.host.details.endpoint.sensorversion": "センサーバージョン", "xpack.securitySolution.host.details.firstSeenTitle": "初回の認識", "xpack.securitySolution.host.details.lastSeenTitle": "前回の認識", "xpack.securitySolution.host.details.overview.cloudProviderTitle": "クラウドプロバイダー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 49f1232fad6e1a..59b9675ec7e775 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1479,7 +1479,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "查看单个文档", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "查看周围文档", "discover.documentsAriaLabel": "文档", - "discover.docViews.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", "discover.docViews.json.jsonTitle": "JSON", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", @@ -5254,7 +5253,6 @@ "xpack.apm.serviceOverview.errorsTableTitle": "错误", "xpack.apm.serviceOverview.instancesTableColumnCpuUsage": "CPU 使用率(平均值)", "xpack.apm.serviceOverview.instancesTableColumnErrorRate": "错误率", - "xpack.apm.serviceOverview.instancesTableColumnLatency": "延迟", "xpack.apm.serviceOverview.instancesTableColumnMemoryUsage": "内存使用率(平均值)", "xpack.apm.serviceOverview.instancesTableColumnNodeName": "节点名称", "xpack.apm.serviceOverview.instancesTableColumnThroughput": "流量", @@ -5267,9 +5265,6 @@ "xpack.apm.serviceOverview.throughtputChartTitle": "流量", "xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "错误率", "xpack.apm.serviceOverview.transactionsTableColumnImpact": "影响", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.avg": "延迟(平均值)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p95": "延迟(第 95 个)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p99": "延迟(第 99 个)", "xpack.apm.serviceOverview.transactionsTableColumnName": "名称", "xpack.apm.serviceOverview.transactionsTableLinkText": "查看事务", "xpack.apm.serviceOverview.transactionsTableTitle": "事务", @@ -17282,7 +17277,6 @@ "xpack.securitySolution.case.caseView.reporterLabel": "报告者", "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "需要更新 { externalService } 事件", "xpack.securitySolution.case.caseView.sendEmalLinkAria": "单击可向 {user} 发送电子邮件", - "xpack.securitySolution.case.caseView.showAlertDeletedTooltip": "未找到告警", "xpack.securitySolution.case.caseView.showAlertTooltip": "显示告警详情", "xpack.securitySolution.case.caseView.statusLabel": "状态", "xpack.securitySolution.case.caseView.tags": "标签", @@ -18910,8 +18904,6 @@ "xpack.securitySolution.hooks.useAddToTimeline.addedFieldMessage": "已将 {fieldOrValue} 添加到时间线", "xpack.securitySolution.host.details.architectureLabel": "架构", "xpack.securitySolution.host.details.endpoint.endpointPolicy": "集成", - "xpack.securitySolution.host.details.endpoint.policyStatus": "配置状态", - "xpack.securitySolution.host.details.endpoint.sensorversion": "感应器版本", "xpack.securitySolution.host.details.firstSeenTitle": "首次看到时间", "xpack.securitySolution.host.details.lastSeenTitle": "最后看到时间", "xpack.securitySolution.host.details.overview.cloudProviderTitle": "云服务提供商", diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx index 4efe0f866a5496..2f776f3937b502 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx @@ -44,12 +44,8 @@ interface ReindexFlyoutState { currentFlyoutStep: ReindexFlyoutStep; } -// eslint-disable-next-line @typescript-eslint/naming-convention -const getOpenAndCloseIndexDocLink = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => ( - +const getOpenAndCloseIndexDocLink = (docLinks: DocLinksStart) => ( + {i18n.translate( 'xpack.upgradeAssistant.checkupTab.reindexing.flyout.openAndCloseDocumentation', { defaultMessage: 'documentation' } diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts index c2bb27ce995ebe..a5da4741b10eb0 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -96,12 +96,12 @@ describe('cluster checkup API', () => { it('returns an 500 error if it throws', async () => { MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'get', - pathPattern: '/api/upgrade_assistant/status', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts index be8d1739341825..b4dae6ec385b45 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -55,7 +55,7 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: return response.forbidden(e.message); } - return response.internalError({ body: e }); + throw e; } } ) diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index 0e0dd075624a68..0b595df0dc8c45 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -54,12 +54,12 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster .getSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'get', - pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); @@ -80,12 +80,12 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster .putSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(routeHandlerContextMock, { body: { isEnabled: false } }, kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(routeHandlerContextMock, { body: { isEnabled: false } }, kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts index 298a87d962ff60..8b427c6443ac88 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -30,12 +30,8 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) request, response ) => { - try { - const result = await getDeprecationLoggingStatus(client); - return response.ok({ body: result }); - } catch (e) { - return response.internalError({ body: e }); - } + const result = await getDeprecationLoggingStatus(client); + return response.ok({ body: result }); } ) ); @@ -59,14 +55,10 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) request, response ) => { - try { - const { isEnabled } = request.body as { isEnabled: boolean }; - return response.ok({ - body: await setDeprecationLogging(client, isEnabled), - }); - } catch (e) { - return response.internalError({ body: e }); - } + const { isEnabled } = request.body as { isEnabled: boolean }; + return response.ok({ + body: await setDeprecationLogging(client, isEnabled), + }); } ) ); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index aa8e42803121ca..d36f8225ffa3b2 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -66,7 +66,7 @@ const mapAnyErrorToKibanaHttpResponse = (e: any) => { return kibanaResponseFactory.notFound({ body: e.message }); case CannotCreateIndex: case ReindexTaskCannotBeDeleted: - return kibanaResponseFactory.internalError({ body: e.message }); + throw e; case ReindexTaskFailed: // Bad data return kibanaResponseFactory.customError({ body: e.message, statusCode: 422 }); @@ -78,7 +78,7 @@ const mapAnyErrorToKibanaHttpResponse = (e: any) => { // nothing matched } } - return kibanaResponseFactory.internalError({ body: e }); + throw e; }; export function registerReindexIndicesRoutes( diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts index 96c056ebe5ee8e..05ad542ec9c000 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts @@ -92,20 +92,20 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIOpenOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/stats/ui_open', - })( - routeHandlerContextMock, - createRequestMock({ - body: { - overview: false, - }, - }), - kibanaResponseFactory - ); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_open', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + overview: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); }); }); @@ -118,7 +118,7 @@ describe('Upgrade Assistant Telemetry API', () => { stop: false, }; - (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); const resp = await routeDependencies.router.getHandler({ method: 'put', @@ -144,7 +144,7 @@ describe('Upgrade Assistant Telemetry API', () => { stop: true, }; - (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); const resp = await routeDependencies.router.getHandler({ method: 'put', @@ -168,20 +168,20 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIReindexOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/stats/ui_reindex', - })( - routeHandlerContextMock, - createRequestMock({ - body: { - start: false, - }, - }), - kibanaResponseFactory - ); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + start: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index e7c8843c49750b..d24c384f7f0f35 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -24,18 +24,14 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout }, async (ctx, request, response) => { const { cluster, indices, overview } = request.body; - try { - return response.ok({ - body: await upsertUIOpenOption({ - savedObjects: getSavedObjectsService(), - cluster, - indices, - overview, - }), - }); - } catch (e) { - return response.internalError({ body: e }); - } + return response.ok({ + body: await upsertUIOpenOption({ + savedObjects: getSavedObjectsService(), + cluster, + indices, + overview, + }), + }); } ); @@ -53,19 +49,15 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout }, async (ctx, request, response) => { const { close, open, start, stop } = request.body; - try { - return response.ok({ - body: await upsertUIReindexOption({ - savedObjects: getSavedObjectsService(), - close, - open, - start, - stop, - }), - }); - } catch (e) { - return response.internalError({ body: e }); - } + return response.ok({ + body: await upsertUIReindexOption({ + savedObjects: getSavedObjectsService(), + close, + open, + start, + stop, + }), + }); } ); } diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx index b91b50666cf07b..68b9ab0435aaac 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx @@ -15,8 +15,8 @@ import { Position, timeFormatter, Settings, - SeriesIdentifier, BrushEndListener, + LegendItemListener, } from '@elastic/charts'; import { getChartDateLabel } from '../../../lib/helper'; import { LocationDurationLine } from '../../../../common/types'; @@ -75,7 +75,7 @@ export const DurationChartComponent = ({ }); }; - const legendToggleVisibility = (legendItem: SeriesIdentifier | null) => { + const legendToggleVisibility: LegendItemListener = ([legendItem]) => { if (legendItem) { setHiddenLegends((prevState) => { if (prevState.includes(legendItem.specId)) { diff --git a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts index 632bf7423f8415..8b6add27f889a7 100644 --- a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts +++ b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts @@ -39,7 +39,7 @@ export const createRouteWithAuth = ( case 403: return response.forbidden({ body: { message } }); default: - return response.internalError(); + throw new Error('Failed to validate the license'); } }; diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index 6b291a3be9dcde..24e501a1bddb83 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -26,34 +26,22 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ esClient: esClient.asCurrentUser, }); - try { - const res = await uptimeRoute.handler({ - uptimeEsClient, - savedObjectsClient, - context, - request, - response, - }); - - if (res instanceof KibanaResponse) { - return res; - } - - return response.ok({ - body: { - ...res, - }, - }); - } catch (e) { - // please don't remove this, this will be really helpful during debugging - /* eslint-disable-next-line no-console */ - console.error(e); + const res = await uptimeRoute.handler({ + uptimeEsClient, + savedObjectsClient, + context, + request, + response, + }); - return response.internalError({ - body: { - message: e.message, - }, - }); + if (res instanceof KibanaResponse) { + return res; } + + return response.ok({ + body: { + ...res, + }, + }); }, }); diff --git a/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts index d4fb0016aada02..b234bed9f7d4dc 100644 --- a/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts @@ -86,7 +86,7 @@ export function registerGetRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts b/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts index f6803767d89ff1..0882fc3a65027a 100644 --- a/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts @@ -57,7 +57,7 @@ export function registerListFieldsRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts b/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts index 8ab40b346e9794..629f29734c6031 100644 --- a/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts @@ -69,7 +69,7 @@ export function registerLoadHistoryRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts index 5e289edecefffd..ef5c7c6177ce97 100644 --- a/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts @@ -36,7 +36,7 @@ export function registerLoadRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts index f7ef97c151b2fb..1afec0ada91043 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts @@ -61,7 +61,7 @@ export function registerAcknowledgeRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts index 6d0a2a70850255..85d1d0c51f0b3f 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts @@ -57,7 +57,7 @@ export function registerActivateRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts index 89497bd092f320..071c9d17beee17 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts @@ -57,7 +57,7 @@ export function registerDeactivateRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts index c1e36f5d9c62ff..ebf5b41bc589c4 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts @@ -44,7 +44,7 @@ export function registerDeleteRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts index f8eb3df6126303..e2078ac5cc1d94 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts @@ -74,7 +74,7 @@ export function registerExecuteRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts index ac19d0f71e31c4..cafcf81511a4fa 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts @@ -94,7 +94,7 @@ export function registerHistoryRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts index 350831d32814cf..bba60cf93054cc 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts @@ -61,7 +61,7 @@ export function registerLoadRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts index 9b5d3b4615c07d..b4a219979e6502 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts @@ -62,7 +62,7 @@ export function registerSaveRoute(deps: RouteDependencies) { } catch (e) { const es404 = isEsError(e) && e.statusCode === 404; if (!es404) { - return response.internalError({ body: e }); + throw e; } // Else continue... } @@ -97,7 +97,7 @@ export function registerSaveRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts index 3b2050bff15b59..0310d7eed9d344 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts @@ -62,7 +62,7 @@ export function registerVisualizeRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts index fad293715b9bb8..631f6fdcb0903d 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts @@ -51,12 +51,8 @@ export function registerDeleteRoute(deps: RouteDependencies) { }, }, licensePreRoutingFactory(deps, async (ctx, request, response) => { - try { - const results = await deleteWatches(ctx.watcher!.client, request.body.watchIds); - return response.ok({ body: { results } }); - } catch (e) { - return response.internalError({ body: e }); - } + const results = await deleteWatches(ctx.watcher!.client, request.body.watchIds); + return response.ok({ body: { results } }); }) ); } diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts b/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts index f1119219dae3cd..6a4e85800fa8d5 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts @@ -75,7 +75,7 @@ export function registerListRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.ts index b29c52cd011c48..9117637b70bee6 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.ts @@ -53,7 +53,7 @@ export function registerSettingsRoute({ | KibanaSettingsCollector | undefined; if (!settingsCollector) { - return res.internalError(); + throw new Error('The settings collector is not registered'); } const settings = diff --git a/x-pack/test/accessibility/apps/canvas.ts b/x-pack/test/accessibility/apps/canvas.ts new file mode 100644 index 00000000000000..c802d62b05bf94 --- /dev/null +++ b/x-pack/test/accessibility/apps/canvas.ts @@ -0,0 +1,60 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const { common } = getPageObjects(['common']); + + describe('Canvas', () => { + before(async () => { + await esArchiver.load('canvas/default'); + await common.navigateToApp('canvas'); + }); + + it('loads workpads', async function () { + await retry.waitFor( + 'canvas workpads visible', + async () => await testSubjects.exists('canvasWorkpadLoaderTable') + ); + await a11y.testAppSnapshot(); + }); + + it('provides bulk actions', async function () { + await testSubjects.click('checkboxSelectAll'); + await retry.waitFor( + 'canvas bulk actions visible', + async () => await testSubjects.exists('deleteWorkpadButton') + ); + await a11y.testAppSnapshot(); + }); + + it('can delete workpads', async function () { + await testSubjects.click('deleteWorkpadButton'); + await retry.waitFor( + 'canvas delete modal visible', + async () => await testSubjects.exists('canvasConfirmModal') + ); + await a11y.testAppSnapshot(); + }); + + it('can navigate to templates', async function () { + await testSubjects.click('confirmModalCancelButton'); // close modal from previous test + + await testSubjects.click('workpadTemplates'); + await retry.waitFor( + 'canvas templates visible', + async () => await testSubjects.exists('canvasTemplatesTable') + ); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/apps/kibana_overview.ts b/x-pack/test/accessibility/apps/kibana_overview.ts index 068b600d2adf2f..fe255a3413bd0f 100644 --- a/x-pack/test/accessibility/apps/kibana_overview.ts +++ b/x-pack/test/accessibility/apps/kibana_overview.ts @@ -20,23 +20,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await PageObjects.home.removeSampleDataSet('flights'); await esArchiver.unload('empty_kibana'); }); - it('Getting started view', async () => { - await a11y.testAppSnapshot(); - }); - - it('Overview view', async () => { - await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await PageObjects.home.addSampleDataSet('flights'); - await PageObjects.common.navigateToApp('kibanaOverview'); + it('Kibana overview', async () => { await a11y.testAppSnapshot(); }); }); diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 24c46c1a1687e6..b014f672ed5a64 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -18,6 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./apps/login_page'), require.resolve('./apps/home'), + require.resolve('./apps/kibana_overview'), require.resolve('./apps/grok_debugger'), require.resolve('./apps/search_profiler'), require.resolve('./apps/uptime'), @@ -26,12 +27,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/dashboard_edit_panel'), require.resolve('./apps/users'), require.resolve('./apps/roles'), - require.resolve('./apps/kibana_overview'), require.resolve('./apps/ingest_node_pipelines'), require.resolve('./apps/index_lifecycle_management'), require.resolve('./apps/ml'), require.resolve('./apps/lens'), require.resolve('./apps/upgrade_assistant'), + require.resolve('./apps/canvas'), ], pageObjects, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts index 18e813d30659b1..778c87f0ed03ec 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts @@ -42,23 +42,19 @@ export class FixturePlugin implements Plugin, res: KibanaResponseFactory ): Promise> { - try { - let namespace: string | undefined; - if (spaces && req.body.spaceId) { - namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); - } - const [, { encryptedSavedObjects }] = await core.getStartServices(); - await encryptedSavedObjects - .getClient({ - includedHiddenTypes: ['alert', 'action'], - }) - .getDecryptedAsInternalUser(req.body.type, req.body.id, { - namespace, - }); - return res.ok({ body: { success: true } }); - } catch (err) { - return res.internalError({ body: err }); + let namespace: string | undefined; + if (spaces && req.body.spaceId) { + namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); } + const [, { encryptedSavedObjects }] = await core.getStartServices(); + await encryptedSavedObjects + .getClient({ + includedHiddenTypes: ['alert', 'action'], + }) + .getDecryptedAsInternalUser(req.body.type, req.body.id, { + namespace, + }); + return res.ok({ body: { success: true } }); } ); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index 7eacc9ba6f0cb3..d9e362a99e6488 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -66,7 +66,7 @@ export function defineRoutes(core: CoreSetup) { const user = await security.authc.getCurrentUser(req); if (!user) { - return res.internalError({}); + throw new Error('Failed to get the current user'); } // Create an API key using the new grant API - in this case the Kibana system user is creating the @@ -78,7 +78,7 @@ export function defineRoutes(core: CoreSetup) { }); if (!createAPIKeyResult) { - return res.internalError({}); + throw new Error('Failed to grant an API Key'); } const result = await savedObjectsWithAlerts.update( diff --git a/x-pack/test/api_integration/apis/lens/field_stats.ts b/x-pack/test/api_integration/apis/lens/field_stats.ts index ac4ebb4e5b02c2..94960b98591219 100644 --- a/x-pack/test/api_integration/apis/lens/field_stats.ts +++ b/x-pack/test/api_integration/apis/lens/field_stats.ts @@ -22,29 +22,29 @@ export default ({ getService }: FtrProviderContext) => { describe('index stats apis', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.loadIfNeeded('visualize/default'); - await esArchiver.loadIfNeeded('pre_calculated_histogram'); }); after(async () => { await esArchiver.unload('logstash_functional'); - await esArchiver.unload('visualize/default'); - await esArchiver.unload('pre_calculated_histogram'); }); describe('field distribution', () => { + before(async () => { + await esArchiver.loadIfNeeded('visualize/default'); + }); + after(async () => { + await esArchiver.unload('visualize/default'); + }); + it('should return a 404 for missing index patterns', async () => { await supertest - .post('/api/lens/index_stats/logstash/field') + .post('/api/lens/index_stats/123/field') .set(COMMON_HEADERS) .send({ dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(404); }); @@ -57,10 +57,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(200); @@ -75,11 +72,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(200); @@ -186,11 +179,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: '@timestamp', - type: 'date', - }, + fieldName: '@timestamp', }) .expect(200); @@ -223,11 +212,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'geo.src', - type: 'string', - }, + fieldName: 'geo.src', }) .expect(200); @@ -290,11 +275,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'ip', - type: 'ip', - }, + fieldName: 'ip', }) .expect(200); @@ -349,6 +330,113 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('should return histograms for scripted date fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'scripted_date', + }) + .expect(200); + + expect(body).to.eql({ + histogram: { + buckets: [ + { + count: 4634, + key: 0, + }, + ], + }, + totalDocuments: 4634, + }); + }); + + it('should return top values for scripted string fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'scripted_string', + }) + .expect(200); + + expect(body).to.eql({ + totalDocuments: 4634, + sampledDocuments: 4634, + sampledValues: 4634, + topValues: { + buckets: [ + { + count: 4634, + key: 'hello', + }, + ], + }, + }); + }); + + it('should return top values for index pattern runtime string fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'runtime_string_field', + }) + .expect(200); + + expect(body).to.eql({ + totalDocuments: 4634, + sampledDocuments: 4634, + sampledValues: 4634, + topValues: { + buckets: [ + { + count: 4634, + key: 'hello world!', + }, + ], + }, + }); + }); + + it('should apply filters and queries', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { + bool: { + filter: [{ match: { 'geo.src': 'US' } }], + }, + }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'bytes', + }) + .expect(200); + + expect(body.totalDocuments).to.eql(425); + }); + }); + + describe('histogram', () => { + before(async () => { + await esArchiver.loadIfNeeded('pre_calculated_histogram'); + }); + after(async () => { + await esArchiver.unload('pre_calculated_histogram'); + }); + it('should return an auto histogram for precalculated histograms', async () => { const { body } = await supertest .post('/api/lens/index_stats/histogram-test/field') @@ -357,10 +445,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'histogram-content', - type: 'histogram', - }, + fieldName: 'histogram-content', }) .expect(200); @@ -428,10 +513,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match: { 'histogram-title': 'single value' } }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'histogram-content', - type: 'histogram', - }, + fieldName: 'histogram-content', }) .expect(200); @@ -443,95 +525,6 @@ export default ({ getService }: FtrProviderContext) => { topValues: { buckets: [] }, }); }); - - it('should return histograms for scripted date fields', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { match_all: {} }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'scripted date', - type: 'date', - scripted: true, - script: '1234', - lang: 'painless', - }, - }) - .expect(200); - - expect(body).to.eql({ - histogram: { - buckets: [ - { - count: 4634, - key: 0, - }, - ], - }, - totalDocuments: 4634, - }); - }); - - it('should return top values for scripted string fields', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { match_all: {} }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'scripted string', - type: 'string', - scripted: true, - script: 'return "hello"', - lang: 'painless', - }, - }) - .expect(200); - - expect(body).to.eql({ - totalDocuments: 4634, - sampledDocuments: 4634, - sampledValues: 4634, - topValues: { - buckets: [ - { - count: 4634, - key: 'hello', - }, - ], - }, - }); - }); - - it('should apply filters and queries', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { - bool: { - filter: [{ match: { 'geo.src': 'US' } }], - }, - }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, - }) - .expect(200); - - expect(body.totalDocuments).to.eql(425); - }); }); }); }; diff --git a/x-pack/test/api_integration/apis/security_solution/hosts.ts b/x-pack/test/api_integration/apis/security_solution/hosts.ts index 3db60dc2bf93cc..17f0bb58be0a91 100644 --- a/x-pack/test/api_integration/apis/security_solution/hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/hosts.ts @@ -142,24 +142,64 @@ export default function ({ getService }: FtrProviderContext) { expect(hostDetails).to.eql(expectedHostDetails); }); - it('Make sure that we get Last First Seen for a Host', async () => { + it('Make sure that we get First Seen for a Host without docValueFields', async () => { const { body: firstLastSeenHost } = await supertest .post('/internal/search/securitySolutionSearchStrategy/') .set('kbn-xsrf', 'true') .send({ - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], docValueFields: [], hostName: 'zeek-sensor-san-francisco', + order: 'asc', }) .expect(200); - const expected = { - firstSeen: '2019-02-19T19:36:23.561Z', - lastSeen: '2019-02-19T20:42:33.561Z', - }; + expect(firstLastSeenHost.firstSeen).to.eql('2019-02-19T19:36:23.561Z'); + }); + + it('Make sure that we get Last Seen for a Host without docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [], + hostName: 'zeek-sensor-san-francisco', + order: 'desc', + }) + .expect(200); + expect(firstLastSeenHost.lastSeen).to.eql('2019-02-19T20:42:33.561Z'); + }); - expect(firstLastSeenHost.firstSeen).to.eql(expected.firstSeen); - expect(firstLastSeenHost.lastSeen).to.eql(expected.lastSeen); + it('Make sure that we get First Seen for a Host with docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [{ field: '@timestamp', format: 'epoch_millis' }], + hostName: 'zeek-sensor-san-francisco', + order: 'asc', + }) + .expect(200); + expect(firstLastSeenHost.firstSeen).to.eql(new Date('2019-02-19T19:36:23.561Z').valueOf()); + }); + + it('Make sure that we get Last Seen for a Host with docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [{ field: '@timestamp', format: 'epoch_millis' }], + hostName: 'zeek-sensor-san-francisco', + order: 'desc', + }) + .expect(200); + expect(firstLastSeenHost.lastSeen).to.eql(new Date('2019-02-19T20:42:33.561Z').valueOf()); }); }); } diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json index 90335d303a36d8..f1cc32f33dd2c9 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json @@ -977,7 +977,51 @@ }, "indices": 1, "plugins": {} - } + }, + "logstash": { + "count": 1, + "cluster_stats": { + "collection_types": { + "internal_collection": 1 + }, + "pipelines": { + "batch_size_min": 125, + "batch_size_max": 125, + "batch_size_avg": 125, + "batch_size_total": 125, + "count": 1, + "sources": { + "string": true + }, + "workers_avg": 4, + "workers_total": 4, + "workers_min": 4, + "workers_max": 4 + }, + "plugins": [ + { + "count": 1, + "name": "logstash-input-twitter" + }, + { + "count": 1, + "name": "logstash-output-stdout" + }, + { + "count": 1, + "name": "logstash-output-elasticsearch" + } + ], + "queues": { + "memory": 1 + } + }, + "versions": [ + { + "count": 1, + "version": "7.0.0-alpha1" + } + ]} }, "timestamp": "2017-08-15T22:10:52.642Z", "version": "7.0.0-alpha1", diff --git a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index 1d00d54a3997bf..4474d0996175bb 100644 --- a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -31,7 +31,7 @@ export default function rumHasDataApiTests({ getService }: FtrProviderContext) { 'has RUM data with data', { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { - it('returns that it has data and service name with most traffice', async () => { + it('returns that it has data and service name with most traffic', async () => { const response = await supertest.get( '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=' ); diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index db2b42323c4f64..45114dd506716d 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -109,28 +109,28 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/throughput?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/throughput?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 7c1b01d2715b66..cca40a6950007f 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -35,6 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-java/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, @@ -63,6 +64,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-java/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, @@ -146,6 +148,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-ruby/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index efd0b410cf8aaa..3afaec653fcb35 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -346,8 +346,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( - `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` + `/api/apm/services?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&uiFilters=${encodeURIComponent( + `{"kuery":"service.name:opbeans-java"}` )}` ); }); diff --git a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index c8104b9858027c..ad9eca1cc0900c 100644 --- a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -995,7 +995,7 @@ Array [ }, "serviceName": "kibana-frontend", "transactionName": "/app/dev_tools", - "transactionType": "page-load", + "transactionType": "route-change", "transactionsPerMinute": 0.0666666666666667, }, Object { diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index a384ca2c9364e8..11c557fd02e381 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environment is selected should return the correct anomaly boundaries 1`] = ` Array [ Object { "x": 1607436000000, @@ -30,7 +30,7 @@ Array [ ] `; -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected should return a non-empty anomaly series 1`] = ` Array [ Object { "x": 1607436000000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts index e46ec7a181fc00..56d5e217068a4b 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts @@ -83,16 +83,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { .toMatchInline(` Array [ Object { - "traceId": "af0f18dc0841cfc1f567e7e1d55cfda7", - "transactionId": "925f02e5ac122897", + "traceId": "a4eb3781a21dc11d289293076fd1a1b3", + "transactionId": "21892bde4ff1364d", }, Object { "traceId": "ccd327537120e857bdfa407434dfb9a4", "transactionId": "c5f923159cc1b8a6", }, Object { - "traceId": "a4eb3781a21dc11d289293076fd1a1b3", - "transactionId": "21892bde4ff1364d", + "traceId": "af0f18dc0841cfc1f567e7e1d55cfda7", + "transactionId": "925f02e5ac122897", }, ] `); diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index 3003c6f902f39e..523139717b309e 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -26,10 +26,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Latency with a basic license when data is not loaded ', { config: 'basic', archives: [] }, () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); it('returns 400 when latencyAggregationType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` ); expect(response.status).to.be(400); @@ -37,7 +37,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when transactionType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); expect(response.status).to.be(400); @@ -45,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` ); expect(response.status).to.be(200); @@ -62,12 +62,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { let response: PromiseReturnType; - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); describe('average latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` ); }); @@ -81,7 +81,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('95th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` ); }); @@ -95,7 +95,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('99th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` ); }); @@ -116,15 +116,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { const transactionType = 'request'; - describe('without environment', () => { + describe('without an environment', () => { const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); - it('should return an error response', () => { - expect(response.status).to.eql(400); + + it('returns an ok response', () => { + expect(response.status).to.eql(200); }); }); @@ -139,11 +140,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('with environment selected in uiFilters', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); + describe('with environment selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -166,13 +167,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('when not defined environments seleted', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) - ); + describe('when not defined environment is selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-python/transactions/charts/latency?environment=ENVIRONMENT_NOT_DEFINED&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -195,10 +194,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('with all environments selected', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -212,12 +211,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('with environment selected and empty kuery filter', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ kuery: '', environment: 'production' }) - ); + const uiFilters = encodeURIComponent(JSON.stringify({ kuery: '' })); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts index 040e280e3157f2..430392a32bfb82 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts @@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { // url parameters const { start, end } = metadata; - const uiFilters = JSON.stringify({ environment: 'testing' }); + const uiFilters = JSON.stringify({}); registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { @@ -28,6 +28,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, query: { + environment: 'testing', start, end, uiFilters, @@ -53,6 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, query: { + environment: 'testing', start, end, uiFilters, diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts index 2250b481c37291..86b1c3031cbef7 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts @@ -95,6 +95,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -110,6 +114,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.generatedAlert, alerts: [{ _id: 'id1' }], index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -167,6 +175,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'new-id', index: postCommentAlertReq.index, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); @@ -230,6 +242,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -302,6 +318,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, }; for (const attribute of ['alertId', 'index']) { @@ -341,6 +361,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, [attribute]: attribute, }) .expect(400); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts index 1ce011985d9e63..fb095c117cdfb0 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts @@ -148,6 +148,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, }; for (const attribute of ['alertId', 'index']) { @@ -176,6 +180,10 @@ export default ({ getService }: FtrProviderContext): void => { [attribute]: attribute, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); } @@ -296,6 +304,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -346,6 +358,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts index dcc49152e4db8c..43d6be196da0d6 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts @@ -402,6 +402,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -453,6 +457,10 @@ export default ({ getService }: FtrProviderContext): void => { alertId: alert._id, index: alert._index, type: CommentType.alert, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); @@ -503,6 +511,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -570,6 +582,10 @@ export default ({ getService }: FtrProviderContext): void => { alertId: alert._id, index: alert._index, type: CommentType.alert, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); diff --git a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts index 01dd6ed5404c2e..4812ead2c4c787 100644 --- a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts +++ b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts @@ -736,7 +736,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: caseRes.body.id, - comment: { alertId: alert._id, index: alert._index, type: CommentType.alert }, + comment: { + alertId: alert._id, + index: alert._index, + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; @@ -784,7 +789,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); createdActionId = createdAction.id; - const comment = { alertId: 'test-id', index: 'test-index', type: CommentType.alert }; + const comment = { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }; const params = { subAction: 'addComment', subActionParams: { @@ -876,7 +886,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: '123', - comment: { alertId: 'test-id', index: 'test-index', type: CommentType.alert }, + comment: { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; @@ -1017,7 +1032,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: caseRes.body.id, - comment: { alertId: 'test-id', index: 'test-index', type: CommentType.alert }, + comment: { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; diff --git a/x-pack/test/case_api_integration/common/lib/mock.ts b/x-pack/test/case_api_integration/common/lib/mock.ts index 2f4fa1b30f5649..f6fd2b1a6b3bed 100644 --- a/x-pack/test/case_api_integration/common/lib/mock.ts +++ b/x-pack/test/case_api_integration/common/lib/mock.ts @@ -8,6 +8,7 @@ import { CommentSchemaType, ContextTypeGeneratedAlertType, + createAlertsString, isCommentGeneratedAlert, transformConnectorComment, } from '../../../../plugins/case/server/connectors'; @@ -70,12 +71,15 @@ export const postCommentUserReq: CommentRequestUserType = { export const postCommentAlertReq: CommentRequestAlertType = { alertId: 'test-id', index: 'test-index', + rule: { id: 'test-rule-id', name: 'test-index-id' }, type: CommentType.alert, }; export const postCommentGenAlertReq: ContextTypeGeneratedAlertType = { - alerts: [{ _id: 'test-id' }, { _id: 'test-id2' }], - index: 'test-index', + alerts: createAlertsString([ + { _id: 'test-id', _index: 'test-index', ruleId: 'rule-id', ruleName: 'rule name' }, + { _id: 'test-id2', _index: 'test-index', ruleId: 'rule-id', ruleName: 'rule name' }, + ]), type: CommentType.generatedAlert, }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index a9120bde274f24..8d494724d9f766 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -27,11 +27,13 @@ import { getSimpleMlRuleOutput, waitForRuleSuccessOrStatus, waitForSignalsToBePresent, + waitForAlertToComplete, getRuleForSignalTesting, getRuleForSignalTestingWithTimestampOverride, } from '../../utils'; import { ROLES } from '../../../../plugins/security_solution/common/test'; import { createUserAndRole, deleteUserAndRole } from '../roles_users_utils'; +import { RuleStatusResponse } from '../../../../plugins/security_solution/server/lib/detection_engine/rules/types'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -287,6 +289,36 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllAlerts(supertest); await esArchiver.unload('security_solution/timestamp_override'); }); + + it('should create a single rule which has a timestamp override for an index pattern that does not exist and write a warning status', async () => { + // defaults to event.ingested timestamp override. + // event.ingested is one of the timestamp fields set on the es archive data + // inside of x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz + const simpleRule = getRuleForSignalTestingWithTimestampOverride(['myfakeindex-1']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + const bodyId = body.id; + + await waitForAlertToComplete(supertest, bodyId); + await waitForRuleSuccessOrStatus(supertest, bodyId, 'warning'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [bodyId] }) + .expect(200); + + expect((statusBody as RuleStatusResponse)[bodyId].current_status?.status).to.eql('warning'); + expect( + (statusBody as RuleStatusResponse)[bodyId].current_status?.last_success_message + ).to.eql( + 'The following indices are missing the timestamp override field "event.ingested": ["myfakeindex-1"]' + ); + }); + it('should create a single rule which has a timestamp override and generates two signals with a "warning" status', async () => { // defaults to event.ingested timestamp override. // event.ingested is one of the timestamp fields set on the es archive data diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 4875e837556fcc..684cbb6368ad90 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -964,6 +964,19 @@ export const getRule = async ( return body; }; +export const waitForAlertToComplete = async ( + supertest: SuperTest, + id: string +): Promise => { + await waitFor(async () => { + const { body: alertBody } = await supertest + .get(`/api/alerts/alert/${id}/state`) + .set('kbn-xsrf', 'true') + .expect(200); + return alertBody.previousStartedAt != null; + }, 'waitForAlertToComplete'); +}; + /** * Waits for the rule in find status to be 'succeeded' * or the provided status, before continuing diff --git a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json index 7e7084698b7889..319a2e95e47bb3 100644 --- a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json @@ -602,25 +602,83 @@ "cluster_uuid": { "type": "keyword" }, - "logstash_state": { + "timestamp": { + "type": "date", + "format": "date_time" + }, + "interval_ms": { + "type": "long" + }, + "type": { + "type": "keyword" + }, + "source_node": { "properties": { - "pipeline": { + "uuid": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "transport_address": { + "type": "keyword" + }, + "ip": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "date_time" + } + } + }, + "logstash_stats": { + "type": "object", + "properties": { + "logstash": { "properties": { - "hash": { + "uuid": { "type": "keyword" }, - "id": { + "name": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "http_address": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "snapshot": { + "type": "boolean" + }, + "status": { "type": "keyword" + }, + "pipeline": { + "properties": { + "workers": { + "type": "short" + }, + "batch_size": { + "type": "long" + } + } } } - } - } - }, - "logstash_stats": { - "properties": { + }, "events": { "properties": { - "duration_in_millis": { + "filtered": { "type": "long" }, "in": { @@ -628,11 +686,48 @@ }, "out": { "type": "long" + }, + "duration_in_millis": { + "type": "long" } } }, + "timestamp": { + "type": "date" + }, "jvm": { "properties": { + "uptime_in_millis": { + "type": "long" + }, + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } + } + } + }, "mem": { "properties": { "heap_max_in_bytes": { @@ -640,30 +735,50 @@ }, "heap_used_in_bytes": { "type": "long" + }, + "heap_used_percent": { + "type": "long" } } - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "logstash": { - "properties": { - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "os": { "properties": { + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + }, + "15m": { + "type": "half_float" + } + } + } + } + }, "cgroup": { "properties": { + "cpuacct": { + "properties": { + "control_group": { + "type": "keyword" + }, + "usage_nanos": { + "type": "long" + } + } + }, "cpu": { "properties": { + "control_group": { + "type": "keyword" + }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -678,70 +793,103 @@ } } } - }, - "cpuacct": { - "properties": { - "usage_nanos": { - "type": "long" - } - } } } - }, + } + } + }, + "process": { + "properties": { "cpu": { "properties": { - "load_average": { - "properties": { - "15m": { - "type": "half_float" - }, - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - } - } + "percent": { + "type": "long" } } + }, + "max_file_descriptors": { + "type": "long" + }, + "open_file_descriptors": { + "type": "long" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + }, + "type": { + "type": "keyword" } } }, "pipelines": { + "type": "nested", "properties": { + "id": { + "type": "keyword" + }, + "hash": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, "events": { "properties": { - "duration_in_millis": { + "in": { + "type": "long" + }, + "filtered": { "type": "long" }, "out": { "type": "long" + }, + "duration_in_millis": { + "type": "long" + }, + "queue_push_duration_in_millis": { + "type": "long" } } }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, "queue": { "properties": { + "events_count": { + "type": "long" + }, + "type": { + "type": "keyword" + }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" - }, - "type": { - "type": "keyword" } } }, "vertices": { + "type": "nested", "properties": { - "duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" + }, + "pipeline_ephemeral_id": { + "type": "keyword" }, "events_in": { "type": "long" @@ -749,41 +897,109 @@ "events_out": { "type": "long" }, - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "queue_push_duration_in_millis": { - "type": "float" + "type": "long" + }, + "long_counters": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, + "double_gauges": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "value": { + "type": "double" + } + } } - }, - "type": "nested" - } - }, - "type": "nested" - }, - "process": { - "properties": { - "cpu": { + } + }, + "reloads": { "properties": { - "percent": { + "failures": { + "type": "long" + }, + "successes": { "type": "long" } } } } }, - "queue": { + "workers": { + "type": "short" + }, + "batch_size": { + "type": "integer" + } + } + }, + "logstash_state": { + "properties": { + "uuid": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "http_address": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "type": "keyword" + }, + "pipeline": { "properties": { - "events_count": { - "type": "long" + "id": { + "type": "keyword" + }, + "hash": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "workers": { + "type": "short" + }, + "batch_size": { + "type": "integer" + }, + "format": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "representation": { + "enabled": false } } - }, - "timestamp": { - "type": "date" } } }, @@ -799,13 +1015,6 @@ "type": "text" } } - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "type": { - "type": "keyword" } } }, @@ -1003,4 +1212,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json index 26b033e28b4da7..7d0ad0c25f96db 100644 --- a/x-pack/test/functional/es_archives/visualize/default/data.json +++ b/x-pack/test/functional/es_archives/visualize/default/data.json @@ -145,6 +145,31 @@ } } +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "index-pattern:logstash-2015.09.22", + "index": ".kibana_1", + "source": { + "index-pattern": { + "timeFieldName": "@timestamp", + "title": "logstash-2015.09.22", + "fields":"[{\"name\":\"scripted_date\",\"type\":\"date\",\"count\":0,\"scripted\":true,\"script\":\"1234\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"scripted_string\",\"type\":\"string\",\"count\":0,\"scripted\":true,\"script\":\"return 'hello'\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "runtimeFieldMap":"{\"runtime_string_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world!')\"}}}" + }, + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [ + ], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + { "type": "doc", "value": { diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts index 57beb40b164592..7213beb2b49a54 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts @@ -71,20 +71,16 @@ export function initRoutes( req: KibanaRequest, res: KibanaResponseFactory ): Promise> { - try { - const taskManager = await taskManagerStart; - const { task: taskFields } = req.body; - const task = { - ...taskFields, - scope: [scope], - }; + const taskManager = await taskManagerStart; + const { task: taskFields } = req.body; + const task = { + ...taskFields, + scope: [scope], + }; - const taskResult = await taskManager.schedule(task, { req }); + const taskResult = await taskManager.schedule(task, { req }); - return res.ok({ body: taskResult }); - } catch (err) { - return res.internalError({ body: err }); - } + return res.ok({ body: taskResult }); } ); diff --git a/yarn.lock b/yarn.lock index 1a939e3dddf579..4738925e44dfbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2138,10 +2138,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@24.6.0": - version "24.6.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-24.6.0.tgz#2123c72e69e1e4557be41ae55c085a5a9f75d3b6" - integrity sha512-fL0301EcHxJEYRzdlD4JIA3VXY4qwRPSkRrk8hvJNryTlQWEdyXZF3HNczk0IrgST5cfCOGAWG8IVtO59HxUJw== +"@elastic/charts@25.0.1": + version "25.0.1" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-25.0.1.tgz#7c61fc22887b7b1feba3e52fc1b44f21a9c1d0bc" + integrity sha512-UYGO9Yg2+cdJOOs9DjlYeB2Oy/3UMXTtF6/yWZt2iXh7mmW9jI5DqfjgG/9BFSCwQZHUKBZ58d5ZCQfe72maGA== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" @@ -2167,10 +2167,10 @@ version "0.0.0" uid "" -"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary": - version "8.0.0-canary.1" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.1.tgz#5cd0eda62531b71af66a08da6c3cebc26a73d4c0" - integrity sha512-VhQ42wH+0OGmHSlc4It3bqGTL7mLuC2RIionJZBIuY5P6lwUMz7goelfyfTHoo+LStxz5QQ8Zt2xcnAnShTBJg== +"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.2": + version "8.0.0-canary.2" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.2.tgz#476e22bc90fc4f422f7195f693fdcddb7f8e1897" + integrity sha512-xYdVJ1MCAprVxd0rqmkBVof7I0N+e6VBCcr0UOwEYjvpQJTvu6PPQROBAAmtAAgvIKs4a8HmpArGgu5QJUnNjw== dependencies: debug "^4.1.1" hpagent "^0.1.1" @@ -9020,10 +9020,10 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@^5.6.4: - version "5.6.4" - resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.4.tgz#8cf4bc750b26d27161306858ee9069218ad7cdfd" - integrity sha512-ZhuZcGxOBHBXFBCwweVf02b+KhWe0tdgg71jPSl583YYxlru+JBRH+TFM8S0J6/6YUuTWO81M9funjehJ18jqg== +backport@^5.6.6: + version "5.6.6" + resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.6.tgz#cb03f948a36386734fa491343b93f4ca280e00f3" + integrity sha512-8O7z0q6m5DfQgrhLDUOLcH2y/rXwSgqv5WIWSSoZpPyRdPpmd3xApIfohlJ3oBX9W0TdbO3aKzaV00qg3H9P7w== dependencies: "@octokit/rest" "^18.0.12" "@types/lodash.difference" "^4.5.6" @@ -9040,7 +9040,7 @@ backport@^5.6.4: lodash.isstring "^4.0.1" lodash.uniq "^4.5.0" make-dir "^3.1.0" - ora "^5.2.0" + ora "^5.3.0" safe-json-stringify "^1.2.0" strip-json-comments "^3.1.1" winston "^3.3.3" @@ -22186,10 +22186,10 @@ ora@^4.0.3, ora@^4.0.4: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ora@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.2.0.tgz#de10bfd2d15514384af45f3fa9d9b1aaf344fda1" - integrity sha512-+wG2v8TUU8EgzPHun1k/n45pXquQ9fHnbXVetl9rRgO6kjZszGGbraF3XPTIdgeA+s1lbRjSEftAnyT0w8ZMvQ== +ora@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" + integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== dependencies: bl "^4.0.3" chalk "^4.1.0"