Skip to content

Commit

Permalink
[8.x] [Security Solution] Add ES|QL Query editable component (#199887) (
Browse files Browse the repository at this point in the history
#202224)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Add ES|QL Query editable component
(#199887)](#199887)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Maxim
Palenov","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-28T14:48:48Z","message":"[Security
Solution] Add ES|QL Query editable component (#199887)\n\n**Partially
addresses:** https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds is built on top of
#193828 and
#196948 and adds an ES|QL Query
editable component for Three Way Diff tab's final edit side of the
upgrade prebuilt rule workflow.\r\n\r\n## Details\r\n\r\nThis PR
extracts ES|QL Query edit component from Define rule form step and makes
it reusable. The following changes were made\r\n\r\n- ES|QL validator
was refactored and covered by unit tests\r\n- Query persistence was
addressed and covered by tests (previous functionality didn't work out
of the box and didn't have tests)\r\n\r\n## How to test\r\n\r\nThe
simplest way to test is via patching installed prebuilt rules (a.k.a.
downgrading a prebuilt rule) via Rule Patch API. Please follow steps
below\r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled\r\n- Run Kibana locally\r\n- Install an ES|QL prebuilt
rule, e.g. `AWS Bedrock Guardrails Detected Multiple Violations by a
Single User Over a Session` with rule_id
`0cd2f3e6-41da-40e6-b28b-466f688f00a6`\r\n- Patch the installed rule by
running a query below\r\n\r\n```bash\r\ncurl -X PATCH --user
elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'
-H \"elastic-api-version: 2023-10-31\" -d
'{\"rule_id\":\"0cd2f3e6-41da-40e6-b28b-466f688f00a6\",\"version\":1,\"query\":\"from
logs-*\",\"language\":\"esql\"}'
http://localhost:5601/kbn/api/detection_engine/rules\r\n```\r\n\r\n-
Open `Detection Rules (SIEM)` Page -> `Rule Updates` -> click on `AWS
Bedrock Guardrails Detected Multiple Violations by a Single User Over a
Session` rule -> expand `EQL Query` to see EQL Query -> press `Edit`
button\r\n\r\n## Screenshots\r\n\r\n<img width=\"2550\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/f65d04d5-9fd9-4d3f-8741-eba04a3be8a6\">\r\n\r\n<img
width=\"2552\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/dd0a2613-5262-44b2-bbeb-d0ed34d57d9c\">","sha":"e55232f87732594eb9502993766203a219be44db","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection
Rules","backport:version","v8.18.0"],"number":199887,"url":"https://github.com/elastic/kibana/pull/199887","mergeCommit":{"message":"[Security
Solution] Add ES|QL Query editable component (#199887)\n\n**Partially
addresses:** https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds is built on top of
#193828 and
#196948 and adds an ES|QL Query
editable component for Three Way Diff tab's final edit side of the
upgrade prebuilt rule workflow.\r\n\r\n## Details\r\n\r\nThis PR
extracts ES|QL Query edit component from Define rule form step and makes
it reusable. The following changes were made\r\n\r\n- ES|QL validator
was refactored and covered by unit tests\r\n- Query persistence was
addressed and covered by tests (previous functionality didn't work out
of the box and didn't have tests)\r\n\r\n## How to test\r\n\r\nThe
simplest way to test is via patching installed prebuilt rules (a.k.a.
downgrading a prebuilt rule) via Rule Patch API. Please follow steps
below\r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled\r\n- Run Kibana locally\r\n- Install an ES|QL prebuilt
rule, e.g. `AWS Bedrock Guardrails Detected Multiple Violations by a
Single User Over a Session` with rule_id
`0cd2f3e6-41da-40e6-b28b-466f688f00a6`\r\n- Patch the installed rule by
running a query below\r\n\r\n```bash\r\ncurl -X PATCH --user
elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'
-H \"elastic-api-version: 2023-10-31\" -d
'{\"rule_id\":\"0cd2f3e6-41da-40e6-b28b-466f688f00a6\",\"version\":1,\"query\":\"from
logs-*\",\"language\":\"esql\"}'
http://localhost:5601/kbn/api/detection_engine/rules\r\n```\r\n\r\n-
Open `Detection Rules (SIEM)` Page -> `Rule Updates` -> click on `AWS
Bedrock Guardrails Detected Multiple Violations by a Single User Over a
Session` rule -> expand `EQL Query` to see EQL Query -> press `Edit`
button\r\n\r\n## Screenshots\r\n\r\n<img width=\"2550\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/f65d04d5-9fd9-4d3f-8741-eba04a3be8a6\">\r\n\r\n<img
width=\"2552\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/dd0a2613-5262-44b2-bbeb-d0ed34d57d9c\">","sha":"e55232f87732594eb9502993766203a219be44db"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199887","number":199887,"mergeCommit":{"message":"[Security
Solution] Add ES|QL Query editable component (#199887)\n\n**Partially
addresses:** https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds is built on top of
#193828 and
#196948 and adds an ES|QL Query
editable component for Three Way Diff tab's final edit side of the
upgrade prebuilt rule workflow.\r\n\r\n## Details\r\n\r\nThis PR
extracts ES|QL Query edit component from Define rule form step and makes
it reusable. The following changes were made\r\n\r\n- ES|QL validator
was refactored and covered by unit tests\r\n- Query persistence was
addressed and covered by tests (previous functionality didn't work out
of the box and didn't have tests)\r\n\r\n## How to test\r\n\r\nThe
simplest way to test is via patching installed prebuilt rules (a.k.a.
downgrading a prebuilt rule) via Rule Patch API. Please follow steps
below\r\n\r\n- Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled\r\n- Run Kibana locally\r\n- Install an ES|QL prebuilt
rule, e.g. `AWS Bedrock Guardrails Detected Multiple Violations by a
Single User Over a Session` with rule_id
`0cd2f3e6-41da-40e6-b28b-466f688f00a6`\r\n- Patch the installed rule by
running a query below\r\n\r\n```bash\r\ncurl -X PATCH --user
elastic:changeme -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'
-H \"elastic-api-version: 2023-10-31\" -d
'{\"rule_id\":\"0cd2f3e6-41da-40e6-b28b-466f688f00a6\",\"version\":1,\"query\":\"from
logs-*\",\"language\":\"esql\"}'
http://localhost:5601/kbn/api/detection_engine/rules\r\n```\r\n\r\n-
Open `Detection Rules (SIEM)` Page -> `Rule Updates` -> click on `AWS
Bedrock Guardrails Detected Multiple Violations by a Single User Over a
Session` rule -> expand `EQL Query` to see EQL Query -> press `Edit`
button\r\n\r\n## Screenshots\r\n\r\n<img width=\"2550\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/f65d04d5-9fd9-4d3f-8741-eba04a3be8a6\">\r\n\r\n<img
width=\"2552\" alt=\"image\"
src=\"https://github.com/user-attachments/assets/dd0a2613-5262-44b2-bbeb-d0ed34d57d9c\">","sha":"e55232f87732594eb9502993766203a219be44db"}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
  • Loading branch information
maximpn authored Nov 29, 2024
1 parent fd2e0ea commit 7d03ee6
Show file tree
Hide file tree
Showing 59 changed files with 1,375 additions and 906 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { ESQLAst, getAstAndSyntaxErrors } from '@kbn/esql-ast';
import { type ESQLAstQueryExpression, parse } from '@kbn/esql-ast';

export const isAggregatingQuery = (ast: ESQLAst): boolean => {
return ast.some((astItem) => astItem.type === 'command' && astItem.name === 'stats');
};
export const isAggregatingQuery = (astExpression: ESQLAstQueryExpression): boolean =>
astExpression.commands.some((command) => command.name === 'stats');

/**
* compute if esqlQuery is aggregating/grouping, i.e. using STATS...BY command
* @param esqlQuery
* @returns boolean
*/
export const computeIsESQLQueryAggregating = (esqlQuery: string): boolean => {
const { ast } = getAstAndSyntaxErrors(esqlQuery);
return isAggregatingQuery(ast);
const { root } = parse(esqlQuery);
return isAggregatingQuery(root);
};

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import type { FieldHook } from '../../../../shared_imports';
import { FilterBar } from '../../../../common/components/filter_bar';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import type { EqlOptions } from '../../../../../common/search_strategy';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import { useKibana } from '../../../../common/lib/kibana';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar';
import type { EqlQueryBarFooterProps } from './footer';
import { EqlQueryBarFooter } from './footer';
import { getValidationResults } from './validators';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { debounceAsync } from '@kbn/securitysolution-utils';
import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports';
import { UseMultiFields } from '../../../../shared_imports';
import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory';
import { eqlQueryValidatorFactory } from './eql_query_validator_factory';
import { EqlQueryBar } from './eql_query_bar';
import * as i18n from './translations';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar';

interface EqlQueryEditProps {
path: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import { isEmpty } from 'lodash';
import type { FormData, ValidationError, ValidationFunc } from '../../../../shared_imports';
import { KibanaServices } from '../../../../common/lib/kibana';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import type { EqlOptions } from '../../../../../common/search_strategy';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar';
import type { EqlResponseError } from '../../../../common/hooks/eql/api';
import { EQL_ERROR_CODES, validateEql } from '../../../../common/hooks/eql/api';
import { EQL_VALIDATION_REQUEST_ERROR } from './translations';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@
* 2.0.
*/

import React from 'react';
import React, { memo } from 'react';
import { EuiPopover, EuiText, EuiButtonIcon, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import * as i18n from './translations';

import { useBoolState } from '../../../../common/hooks/use_bool_state';
import { useBoolean } from '@kbn/react-hooks';
import { useKibana } from '../../../../common/lib/kibana';
import * as i18n from './translations';

/**
* Icon and popover that gives hint to users how to get started with ES|QL rules
*/
const EsqlInfoIconComponent = () => {
export const EsqlInfoIcon = memo(function EsqlInfoIcon(): JSX.Element {
const { docLinks } = useKibana().services;

const [isPopoverOpen, , closePopover, togglePopover] = useBoolState();
const [isPopoverOpen, { off: closePopover, on: togglePopover }] = useBoolean(false);

const button = (
<EuiButtonIcon iconType="iInCircle" onClick={togglePopover} aria-label={i18n.ARIA_LABEL} />
Expand All @@ -29,13 +27,13 @@ const EsqlInfoIconComponent = () => {
<EuiPopover button={button} isOpen={isPopoverOpen} closePopover={closePopover}>
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent"
id="xpack.securitySolution.ruleManagement.esqlQuery.esqlInfoTooltipContent"
defaultMessage="Check out our {createEsqlRuleTypeLink} to get started using ES|QL rules."
values={{
createEsqlRuleTypeLink: (
<EuiLink href={docLinks.links.securitySolution.createEsqlRuleType} target="_blank">
<FormattedMessage
id="xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink"
id="xpack.securitySolution.ruleManagement.esqlQuery.esqlInfoTooltipLink"
defaultMessage="documentation"
/>
</EuiLink>
Expand All @@ -45,8 +43,4 @@ const EsqlInfoIconComponent = () => {
</EuiText>
</EuiPopover>
);
};

export const EsqlInfoIcon = React.memo(EsqlInfoIconComponent);

EsqlInfoIcon.displayName = 'EsqlInfoIcon';
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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, useMemo } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import type { DataViewBase } from '@kbn/es-query';
import { debounceAsync } from '@kbn/securitysolution-utils';
import type { FieldConfig } from '../../../../shared_imports';
import { UseField } from '../../../../shared_imports';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import { QueryBarField } from '../../../rule_creation_ui/components/query_bar_field';
import { esqlQueryRequiredValidator } from './validators/esql_query_required_validator';
import { esqlQueryValidatorFactory } from './validators/esql_query_validator_factory';
import { EsqlInfoIcon } from './esql_info_icon';
import * as i18n from './translations';

interface EsqlQueryEditProps {
path: string;
fieldsToValidateOnChange?: string | string[];
dataView: DataViewBase;
required?: boolean;
loading?: boolean;
disabled?: boolean;
skipIdColumnCheck?: boolean;
onValidityChange?: (arg: boolean) => void;
}

export const EsqlQueryEdit = memo(function EsqlQueryEdit({
path,
fieldsToValidateOnChange,
dataView,
required = false,
loading = false,
disabled = false,
skipIdColumnCheck,
onValidityChange,
}: EsqlQueryEditProps): JSX.Element {
const queryClient = useQueryClient();
const componentProps = useMemo(
() => ({
isDisabled: disabled,
isLoading: loading,
indexPattern: dataView,
idAria: 'ruleEsqlQueryBar',
dataTestSubj: 'ruleEsqlQueryBar',
onValidityChange,
}),
[dataView, loading, disabled, onValidityChange]
);
const fieldConfig: FieldConfig<FieldValueQueryBar> = useMemo(
() => ({
label: i18n.ESQL_QUERY,
labelAppend: <EsqlInfoIcon />,
fieldsToValidateOnChange: fieldsToValidateOnChange
? [path, fieldsToValidateOnChange].flat()
: undefined,
validations: [
...(required
? [
{
validator: esqlQueryRequiredValidator,
},
]
: []),
{
validator: debounceAsync(
esqlQueryValidatorFactory({ queryClient, skipIdColumnCheck }),
300
),
},
],
}),
[required, path, fieldsToValidateOnChange, queryClient, skipIdColumnCheck]
);

return (
<UseField
path={path}
component={QueryBarField}
componentProps={componentProps}
config={fieldConfig}
/>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 './esql_query_edit';
export * from './validators/error_codes';
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@

import { i18n } from '@kbn/i18n';

export const ESQL_QUERY = i18n.translate(
'xpack.securitySolution.ruleManagement.fields.esqlQuery.label',
{
defaultMessage: 'ES|QL query',
}
);

export const ARIA_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel',
'xpack.securitySolution.ruleManagement.fields.esqlQuery.ariaLabel',
{
defaultMessage: `Open help popover`,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 enum ESQL_ERROR_CODES {
INVALID_ESQL = 'ERR_INVALID_ESQL',
INVALID_SYNTAX = 'ERR_INVALID_SYNTAX',
ERR_MISSING_ID_FIELD_FROM_RESULT = 'ERR_MISSING_ID_FIELD_FROM_RESULT',
}
Original file line number Diff line number Diff line change
@@ -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 { fieldValidators, type FormData, type ValidationFunc } from '../../../../../shared_imports';
import type { FieldValueQueryBar } from '../../../../rule_creation_ui/components/query_bar_field';
import * as i18n from './translations';

export const esqlQueryRequiredValidator: ValidationFunc<FormData, string, FieldValueQueryBar> = (
data
) => {
const { value } = data;
const esqlQuery = value.query.query as string;

return fieldValidators.emptyField(i18n.ESQL_QUERY_VALIDATION_REQUIRED)({
...data,
value: esqlQuery,
});
};
Loading

0 comments on commit 7d03ee6

Please sign in to comment.