Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Detections] Convert EQL validation to use search strategy #79538

Merged
merged 10 commits into from
Oct 7, 2020
2 changes: 2 additions & 0 deletions x-pack/plugins/data_enhanced/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plug
export const plugin = () => new DataEnhancedPlugin();

export { DataEnhancedSetup, DataEnhancedStart };

export { ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY } from '../common';
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ describe('EQL search strategy', () => {
})
);
});

it('passes transport options for an existing request', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch.search(mockContext, { id: 'my-search-id', options: { ignore: [400] } });
const [[, requestOptions]] = mockEqlGet.mock.calls;

expect(mockEqlSearch).not.toHaveBeenCalled();
expect(requestOptions).toEqual(expect.objectContaining({ ignore: [400] }));
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ export const eqlSearchStrategyProvider = (
const eqlClient = context.core.elasticsearch.client.asCurrentUser.eql;
const uiSettingsClient = await context.core.uiSettings.client;
const asyncOptions = getAsyncOptions();
const searchOptions = toSnakeCase({ ...request.options });

if (request.id) {
promise = eqlClient.get({
id: request.id,
...toSnakeCase(asyncOptions),
});
promise = eqlClient.get(
{
id: request.id,
...toSnakeCase(asyncOptions),
},
searchOptions
);
} else {
const { ignoreThrottled, ignoreUnavailable } = await getDefaultSearchParams(
uiSettingsClient
Expand All @@ -48,11 +52,10 @@ export const eqlSearchStrategyProvider = (
...asyncOptions,
...request.params,
});
const searchOptions = toSnakeCase({ ...request.options });

promise = eqlClient.search(
searchParams as EqlSearchStrategyRequest['params'],
searchOptions as EqlSearchStrategyRequest['options']
searchOptions
);
}

Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p
export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`;
export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`;
export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`;
export const DETECTION_ENGINE_EQL_VALIDATION_URL = `${DETECTION_ENGINE_URL}/validate_eql`;
export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`;
export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`;

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as t from 'io-ts';

export const eqlValidationSchema = t.exact(
t.type({
valid: t.boolean,
errors: t.array(t.string),
})
);

export type EqlValidationSchema = t.TypeOf<typeof eqlValidationSchema>;
export * from './validation';
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import get from 'lodash/get';
import has from 'lodash/has';
import { get, has } from 'lodash';
Copy link
Member

Choose a reason for hiding this comment

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

For posterity, we're going back to named imports from defaults:

#74539 -> #78156


const PARSING_ERROR_TYPE = 'parsing_exception';
const VERIFICATION_ERROR_TYPE = 'verification_exception';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EqlValidationSchema } from './eql_validation_schema';

export const getEqlValidationSchemaMock = (): EqlValidationSchema => ({
index: ['index-123'],
query: 'process where process.name == "regsvr32.exe"',
});
export * from './helpers';
56 changes: 39 additions & 17 deletions x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,50 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { HttpStart } from '../../../../../../../src/core/public';
import { DETECTION_ENGINE_EQL_VALIDATION_URL } from '../../../../common/constants';
import { EqlValidationSchema as EqlValidationRequest } from '../../../../common/detection_engine/schemas/request/eql_validation_schema';
import { EqlValidationSchema as EqlValidationResponse } from '../../../../common/detection_engine/schemas/response/eql_validation_schema';
import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public';
import {
EqlSearchStrategyRequest,
EqlSearchStrategyResponse,
} from '../../../../../data_enhanced/common';
import { EQL_SEARCH_STRATEGY } from '../../../../../data_enhanced/public';
import {
getValidationErrors,
isErrorResponse,
isValidationErrorResponse,
} from '../../../../common/search_strategy/eql';

interface ApiParams {
http: HttpStart;
interface Params {
index: string[];
query: string;
data: DataPublicPluginStart;
signal: AbortSignal;
}

export const validateEql = async ({
http,
query,
data,
index,
query,
signal,
}: ApiParams & EqlValidationRequest) => {
return http.fetch<EqlValidationResponse>(DETECTION_ENGINE_EQL_VALIDATION_URL, {
method: 'POST',
body: JSON.stringify({
query,
index,
}),
signal,
});
}: Params): Promise<{ valid: boolean; errors: string[] }> => {
const { rawResponse: response } = await data.search
.search<EqlSearchStrategyRequest, EqlSearchStrategyResponse>(
{
// @ts-expect-error allow_no_indices is missing on EqlSearch
params: { allow_no_indices: true, index: index.join(), body: { query, size: 0 } },
options: { ignore: [400] },
},
{
strategy: EQL_SEARCH_STRATEGY,
abortSignal: signal,
}
)
.toPromise();

if (isValidationErrorResponse(response.body)) {
return { valid: false, errors: getValidationErrors(response.body) };
} else if (isErrorResponse(response.body)) {
throw new Error(JSON.stringify(response.body));
} else {
return { valid: true, errors: [] };
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
*/

import { CoreStart } from '../../../../../../../src/core/public';
import { StartPlugins } from '../../../types';

type GlobalServices = Pick<CoreStart, 'http' | 'uiSettings'>;
type GlobalServices = Pick<CoreStart, 'http' | 'uiSettings'> & Pick<StartPlugins, 'data'>;

export class KibanaServices {
private static kibanaVersion?: string;
private static services?: GlobalServices;

public static init({
http,
data,
kibanaVersion,
uiSettings,
}: GlobalServices & { kibanaVersion: string }) {
this.services = { http, uiSettings };
this.services = { data, http, uiSettings };
this.kibanaVersion = kibanaVersion;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ export const eqlValidator = async (
}

try {
const { http } = KibanaServices.get();
const { data } = KibanaServices.get();
const signal = new AbortController().signal;
const response = await validateEql({ query, http, signal, index });
const response = await validateEql({ data, query, signal, index });

if (response?.valid === false) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
INTERNAL_RULE_ID_KEY,
INTERNAL_IMMUTABLE_KEY,
DETECTION_ENGINE_PREPACKAGED_URL,
DETECTION_ENGINE_EQL_VALIDATION_URL,
} from '../../../../../common/constants';
import { EqlSearchResponse, ShardsResponse } from '../../../types';
import {
Expand All @@ -29,7 +28,6 @@ import { QuerySignalsSchemaDecoded } from '../../../../../common/detection_engin
import { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema';
import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock';
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
import { getEqlValidationSchemaMock } from '../../../../../common/detection_engine/schemas/request/eql_validation_schema.mock';

export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({
signal_ids: ['somefakeid1', 'somefakeid2'],
Expand Down Expand Up @@ -147,13 +145,6 @@ export const getPrepackagedRulesStatusRequest = () =>
path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`,
});

export const eqlValidationRequest = () =>
requestMock.create({
method: 'post',
path: DETECTION_ENGINE_EQL_VALIDATION_URL,
body: getEqlValidationSchemaMock(),
});

export interface FindHit<T = RuleAlertType> {
page: number;
perPage: number;
Expand Down
Loading