Skip to content

Commit

Permalink
Validate EQL search strategy requests without ignore client option
Browse files Browse the repository at this point in the history
Previously (since elastic#77493), we had
been leveraging the following facts:

* EQL search endpoint returns syntax errors as a 400 response
* ES client can specify an `ignore` option to opt out of the "throw
  unsuccessful responses" behavior

in order to perform EQL "validation" in a normal, non-exception control
flow.

This commit effectively replaces that with a try/catch, where we no
longer pass (nor _can_ pass) `ignore` to the search strategy, and
instead indicate we'd like the "validation" behavior via a new
`validate` param. When set to true, we will capture the 400 response
and, if valid, return it as before; in all other situations that syntax
error will result in an error response from the search strategy.
  • Loading branch information
rylnd committed Oct 18, 2023
1 parent ae2680a commit 76ba672
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 28 deletions.
13 changes: 5 additions & 8 deletions src/plugins/data/common/search/strategies/eql_search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@
*/

import type { EqlSearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { TransportResult, TransportRequestOptions } from '@elastic/elasticsearch';
import type { TransportResult } from '@elastic/elasticsearch';

import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../types';

export const EQL_SEARCH_STRATEGY = 'eql';

export type EqlRequestParams = EqlSearchRequest;

export interface EqlSearchStrategyRequest extends IKibanaSearchRequest<EqlRequestParams> {
/**
* @deprecated: use IAsyncSearchOptions.transport instead.
*/
options?: TransportRequestOptions;
export interface EqlRequestParams extends EqlSearchRequest {
validate?: boolean;
}

export type EqlSearchStrategyRequest = IKibanaSearchRequest<EqlRequestParams>;

export type EqlSearchStrategyResponse<T = unknown> = IKibanaSearchResponse<TransportResult<T>>;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
IAsyncSearchOptions,
pollSearch,
} from '../../../../common';
import { toEqlKibanaSearchResponse } from './response_utils';
import {
errorToEqlKibanaSearchResponse,
isEqlValidationResponseError,
toEqlKibanaSearchResponse,
} from './response_utils';
import { EqlSearchResponse } from './types';
import { ISearchStrategy } from '../../types';
import { getDefaultSearchParams } from '../es_search';
Expand Down Expand Up @@ -47,6 +51,7 @@ export const eqlSearchStrategyProvider = (
const { track_total_hits: _, ...defaultParams } = await getDefaultSearchParams(
uiSettingsClient
);
const { validate: isValidationRequest, ...eqlParams } = request.params ?? {};
const params = id
? getCommonDefaultAsyncGetParams(searchConfig, options, {
/* disable until full eql support */ disableSearchSessions: true,
Expand All @@ -57,27 +62,33 @@ export const eqlSearchStrategyProvider = (
...getCommonDefaultAsyncGetParams(searchConfig, options, {
/* disable until full eql support */ disableSearchSessions: true,
}),
...request.params,
...eqlParams,
};
const response = id
? await client.get(
{ ...params, id },
{
...request.options,

try {
const response = id
? await client.get(
{ ...params, id },
{
...options.transport,
signal: options.abortSignal,
meta: true,
}
)
: // @ts-expect-error optional key cannot be used since search doesn't expect undefined
await client.search(params, {
...options.transport,
signal: options.abortSignal,
meta: true,
}
)
: // @ts-expect-error optional key cannot be used since search doesn't expect undefined
await client.search(params as EqlSearchStrategyRequest['params'], {
...request.options,
...options.transport,
signal: options.abortSignal,
meta: true,
});

return toEqlKibanaSearchResponse(response as TransportResult<EqlSearchResponse>);
});
return toEqlKibanaSearchResponse(response as TransportResult<EqlSearchResponse>);
} catch (e) {
if (isValidationRequest && isEqlValidationResponseError(e)) {
return errorToEqlKibanaSearchResponse(e);
} else {
throw e;
}
}
};

const cancel = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import type { TransportResult } from '@elastic/elasticsearch';
import { get } from 'lodash';
import { errors, TransportResult } from '@elastic/elasticsearch';
import { EqlSearchResponse } from './types';
import { EqlSearchStrategyResponse } from '../../../../common';

Expand All @@ -24,3 +25,40 @@ export function toEqlKibanaSearchResponse(
isRunning: response.body.is_running,
};
}

export function errorToEqlKibanaSearchResponse(
response: EqlResponseError
): EqlSearchStrategyResponse {
return {
id: undefined,
rawResponse: response.meta,
isPartial: false,
isRunning: false,
};
}

const PARSING_ERROR_TYPE = 'parsing_exception';
const VERIFICATION_ERROR_TYPE = 'verification_exception';
const MAPPING_ERROR_TYPE = 'mapping_exception';

interface ErrorCause {
type: string;
reason: string;
}

interface EqlErrorBody {
error: ErrorCause & { root_cause: ErrorCause[] };
}

export interface EqlResponseError extends errors.ResponseError {
meta: TransportResult<EqlErrorBody>;
}

const isValidationErrorType = (type: unknown): boolean =>
type === PARSING_ERROR_TYPE || type === VERIFICATION_ERROR_TYPE || type === MAPPING_ERROR_TYPE;

export const isEqlResponseError = (response: unknown): response is EqlResponseError =>
response instanceof errors.ResponseError;

export const isEqlValidationResponseError = (response: unknown): response is EqlResponseError =>
isEqlResponseError(response) && isValidationErrorType(get(response, 'meta.body.error.type'));
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export const validateEql = async ({
params: {
index: dataViewTitle,
body: { query, runtime_mappings: runtimeMappings, size: 0 },
validate: true,
},
options: { ignore: [400] },
},
{
strategy: EQL_SEARCH_STRATEGY,
Expand Down

0 comments on commit 76ba672

Please sign in to comment.