Skip to content

[Security Solution][Detections] Discuss: EQL search strategy#78107

Closed
rylnd wants to merge 1 commit intoelastic:masterfrom
rylnd:just_eql_strategy
Closed

[Security Solution][Detections] Discuss: EQL search strategy#78107
rylnd wants to merge 1 commit intoelastic:masterfrom
rylnd:just_eql_strategy

Conversation

@rylnd
Copy link
Copy Markdown
Contributor

@rylnd rylnd commented Sep 22, 2020

This adds a basic implementation for an EQL search strategy. This is meant to start a conversation; there are still several issues to address, which I've annotated inline below.

General issues:

  • Since EQL is part of the Basic license, where should the strategy live? The data_enhanced plugin, a new x-pack plugin, or do we keep it in security_solution for now?
  • The current search strategies all assume normal ES queries and are typed as such, but EQL sends responses that are incompatible with the underlying type SearchResponse provided by elasticsearch.

Checklist

For maintainers

* Attempts to extend ISearchStrategy to allow EQL requests and responses
* Since the ES search response is incompatible with the EQL Search
  response, we can either try to merge them into one generic type, or
  allow two paths for responses. We're currently on track for the
  latter. There are several issues here, the most pressing of which is
  licensing.
export const isEsResponse = (response: any): response is IEsSearchResponse =>
response && response.rawResponse;

export interface EqlSearchResponse<T> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that this type exists in the elasticsearch package, or at least I couldn't find it: the EQL client is currently typed to return an ApiResponse which makes no assumptions about the response body.

params?: ISearchRequestParams;
export interface IEsSearchRequest<T = Record<string, any>, P = ISearchRequestParams<T>>
extends IKibanaSearchRequest {
params?: P;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows us to override params for EqlSearchStrategyRequest below to be those required by the EQL search endpoint.

import { Timeline } from './timeline/saved_object';
import { TLS } from './tls';
import { MatrixHistogram } from './matrix_histogram';
import { SearchTypes } from './detection_engine/signals/types';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this file's changes; these new EQL typings are temporarily duplicated in both the security_solution and data plugins.

};

export const securityEqlSearchStrategyProvider = (
logger: Logger
Copy link
Copy Markdown
Contributor Author

@rylnd rylnd Sep 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not yet pulling configuration here as I wasn't sure which options (if any) were relevant to EQL queries.

@kibanamachine
Copy link
Copy Markdown
Contributor

kibanamachine commented Sep 22, 2020

💔 Build Failed

Failed CI Steps


Test Failures

X-Pack APM API integration tests (basic).x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts·ts.APM specs (basic) Metrics when data is loaded for opbeans-node returns metrics data CPU usage has correct series overall values

Link to Jenkins

Standard Out

Failed Tests Reporter:
  - Test has failed 4 times on tracked branches: https://github.com/elastic/kibana/issues/77870

[00:00:00]       │
[00:00:00]         └-: APM specs (basic)
[00:00:00]           └-> "before all" hook
[00:01:44]           └-: Metrics
[00:01:44]             └-> "before all" hook
[00:01:44]             └-: when data is loaded
[00:01:44]               └-> "before all" hook
[00:01:44]               └-> "before all" hook
[00:01:44]                 │ info [metrics_8.0.0] Loading "mappings.json"
[00:01:44]                 │ info [metrics_8.0.0] Loading "data.json.gz"
[00:01:44]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-ubuntu-18-tests-xxl-1600736549507444443] [apm-8.0.0-metric-000001] creating index, cause [api], templates [], shards [1]/[0]
[00:01:45]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-ubuntu-18-tests-xxl-1600736549507444443] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[apm-8.0.0-metric-000001][0]]])." previous.health="YELLOW" reason="shards started [[apm-8.0.0-metric-000001][0]]"
[00:01:45]                 │ info [metrics_8.0.0] Created index "apm-8.0.0-metric-000001"
[00:01:45]                 │ debg [metrics_8.0.0] "apm-8.0.0-metric-000001" settings {"index":{"codec":"best_compression","lifecycle":{"name":"apm-rollover-30-days","rollover_alias":"apm-8.0.0-metric"},"mapping":{"total_fields":{"limit":"2000"}},"max_docvalue_fields_search":"200","number_of_replicas":"0","number_of_shards":"1","priority":"100","refresh_interval":"1ms"}}
[00:01:45]                 │ info [metrics_8.0.0] Indexed 2323 docs into "apm-8.0.0-metric-000001"
[00:01:45]               └-: for opbeans-node
[00:01:45]                 └-> "before all" hook
[00:01:45]                 └-: returns metrics data
[00:01:45]                   └-> "before all" hook
[00:01:45]                   └-> "before all" hook
[00:01:46]                   └-> contains CPU usage and System memory usage chart data
[00:01:46]                     └-> "before each" hook: global before each
[00:01:46]                     └-> "before each" hook
[00:01:46]                     └- ✓ pass  (1ms) "APM specs (basic) Metrics when data is loaded for opbeans-node returns metrics data contains CPU usage and System memory usage chart data"
[00:01:46]                   └-> "after each" hook
[00:01:46]                   └-: CPU usage
[00:01:46]                     └-> "before all" hook
[00:01:46]                     └-> "before all" hook
[00:01:46]                     └-> has correct series
[00:01:46]                       └-> "before each" hook: global before each
[00:01:46]                       └-> "before each" hook
[00:01:46]                       └- ✓ pass  (1ms) "APM specs (basic) Metrics when data is loaded for opbeans-node returns metrics data CPU usage has correct series"
[00:01:46]                     └-> "after each" hook
[00:01:46]                     └-> has correct series overall values
[00:01:46]                       └-> "before each" hook: global before each
[00:01:46]                       └-> "before each" hook
[00:01:46]                       └- ✖ fail: APM specs (basic) Metrics when data is loaded for opbeans-node returns metrics data CPU usage has correct series overall values
[00:01:46]                       │       Error: expect(received).toMatchInlineSnapshot(snapshot)
[00:01:46]                       │ 
[00:01:46]                       │ Snapshot name: `when data is loaded for opbeans-node returns metrics data CPU usage has correct series overall values 1`
[00:01:46]                       │ 
[00:01:46]                       │ - Snapshot  - 1
[00:01:46]                       │ + Received  + 1
[00:01:46]                       │ 
[00:01:46]                       │   Array [
[00:01:46]                       │     0.714,
[00:01:46]                       │ -   0.38770000000000004,
[00:01:46]                       │ +   0.3877,
[00:01:46]                       │     0.75,
[00:01:46]                       │     0.2543,
[00:01:46]                       │   ]
[00:01:46]                       │       + expected - actual
[00:01:46]                       │ 
[00:01:46]                       │       -false
[00:01:46]                       │       +true
[00:01:46]                       │       
[00:01:46]                       │       at Assertion.assert (/dev/shm/workspace/parallel/24/kibana/packages/kbn-expect/expect.js:100:11)
[00:01:46]                       │       at Assertion.toMatchInline [as eql] (/dev/shm/workspace/parallel/24/kibana/packages/kbn-expect/expect.js:244:8)
[00:01:46]                       │       at Context.apply (test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts:69:16)
[00:01:46]                       │       at Object.apply (/dev/shm/workspace/parallel/24/kibana/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_function.js:84:30)
[00:01:46]                       │ 
[00:01:46]                       │ 

Stack Trace

{ Error: expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `when data is loaded for opbeans-node returns metrics data CPU usage has correct series overall values 1`

- Snapshot  - 1
+ Received  + 1

  Array [
    0.714,
-   0.38770000000000004,
+   0.3877,
    0.75,
    0.2543,
  ]
    at Assertion.assert (/dev/shm/workspace/parallel/24/kibana/packages/kbn-expect/expect.js:100:11)
    at Assertion.toMatchInline [as eql] (/dev/shm/workspace/parallel/24/kibana/packages/kbn-expect/expect.js:244:8)
    at Context.apply (test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts:69:16)
    at Object.apply (/dev/shm/workspace/parallel/24/kibana/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_function.js:84:30) actual: 'false', expected: 'true', showDiff: true }

Build metrics

distributable file count

id value diff baseline
default 45944 +4 45940

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Comment on lines +48 to +52
rawResponse: {
...response.body,
...dummyResponseFields,
},
rawEqlResponse: response.body,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rawResponse: {
...response.body,
...dummyResponseFields,
},
rawEqlResponse: response.body,
rawResponse: response.body

Applying the above change should reveal the incompatibilities between the expected SearchResponse type and our (currently unofficial) EqlSearchResponse type. To name a few, EQL responses are missing _shards, and hits has no max_score. Additionally, hits does not have its own hits field, but instead has either events or sequences depending on the query's syntax.

I've temporarily satisfied typescript by adding these dummy fields to satisfy IEsSearchResponse['rawResponse'], and added another field to hold our "true" EQL response.

We could address this in a few ways: loosen IEsSearchResponse to require the minimal subset of fields (took, timed_out, etc.), give it a conditional type, or diverge from ISearchStrategy entirely. Guidance on a path forward here would be greatly appreciated.


export const securityEqlSearchStrategyProvider = (
logger: Logger
): ISearchStrategy<EqlSearchStrategyRequest, EqlSearchStrategyResponse> => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is just an oversight, but one other thing I noticed is that while registerSearchStrategy is generic, getSearchStrategy is not and so consumers of divergent search strategies would have to assert their strategy's typings, lest they get a IEsSearchStrategy<any>

*/
isPartial?: boolean;
rawResponse: SearchResponse<Source>;
rawResponse: SearchResponse<T>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only response arguments the search infrastructure cares about are:

  • id - the async search ID provided by elasticsearch
  • isRunning and isPartial which are indicators of the search's execution in ES.
  • took and total_hits for telemetry and potentially any additional meta data about the request's execution.

Otherwise, the response can be completely generic, so we could create a new type for rawResponse (EsSearch response is specific to the ES strategy).

depsStart.data
);

plugins.data.search.registerSearchStrategy(SECURITY_EQL_SEARCH_STRATEGY, eqlSearchStrategy);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the strategy is owned by the data plugin, it should be registered there too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lizozom since EQL is only available in x-pack, should this strategy live in the data_enhanced plugin for now?

@lizozom
Copy link
Copy Markdown
Contributor

lizozom commented Sep 23, 2020

@rylnd

I've made this PR to make the search types more generic.

It enables you to define

interface IEqlSearchRequest extends IKibanaSearchRequest<IEqlRequestParams> {
  ...
}

type IEqlSearchResponse = IKibanaSearchResponse<EqlResponse>;

I also added generic typing to both search and getStrategy functions of the search service.

Please let me know if it helps. Maybe you want to rebase this PR on top of that one and give it a try?

@rylnd
Copy link
Copy Markdown
Contributor Author

rylnd commented Sep 23, 2020

@lizozom thanks so much for these improvements, they're working great.

I'm probably going to just close this PR since it'll need to be rebased on both your work and my own outstanding PR (#77493).

In the meantime, I have a working branch that integrates all three pieces on my fork: rylnd#5. It's currently targeting (a local copy of) your branch, so you can see what the implementation on top of that looks like right now.

Since the implementation is pretty straightforward I don't I need any more cursory review at this point (but correct me if you disagree). While I finalize the async search workflow and add the default search configuration, there's one outstanding question we need to answer:

Since EQL is only supported in x-pack, can/should this strategy live in the data plugin? If not, would the data_enhanced plugin be a more appropriate place for it, or should it live somewhere else entirely?

@lizozom
Copy link
Copy Markdown
Contributor

lizozom commented Sep 24, 2020

@rylnd
Glad it helped!
I think it would be a good idea to merge the strategy in a separate PR, for documentation and release notes purposes.

Regarding your question: data_enhanced/server is definitely right the place.

@rylnd
Copy link
Copy Markdown
Contributor Author

rylnd commented Sep 28, 2020

Closing this in favor of #78645.

@rylnd rylnd closed this Sep 28, 2020
@rylnd rylnd deleted the just_eql_strategy branch September 28, 2020 20:48
@MindyRS MindyRS added the Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. label Sep 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

discuss Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:SIEM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants