@@ -158,28 +178,28 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.registry,
- name: i18n.REGISTRY_NAME,
+ name: eventRendererNames[RowRendererId.registry],
description: i18n.REGISTRY_DESCRIPTION,
example: RegistryExample,
searchableDescription: i18n.REGISTRY_DESCRIPTION,
},
{
id: RowRendererId.system_fim,
- name: i18n.FIM_NAME,
+ name: eventRendererNames[RowRendererId.system_fim],
description: i18n.FIM_DESCRIPTION_PART1,
example: SystemFimExample,
searchableDescription: i18n.FIM_DESCRIPTION_PART1,
},
{
id: RowRendererId.system_file,
- name: i18n.FILE_NAME,
+ name: eventRendererNames[RowRendererId.system_file],
description: i18n.FILE_DESCRIPTION_PART1,
example: SystemFileExample,
searchableDescription: i18n.FILE_DESCRIPTION_PART1,
},
{
id: RowRendererId.system_socket,
- name: i18n.SOCKET_NAME,
+ name: eventRendererNames[RowRendererId.system_socket],
description: (
{i18n.SOCKET_DESCRIPTION_PART1}
@@ -192,7 +212,7 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.suricata,
- name: 'Suricata',
+ name: eventRendererNames[RowRendererId.suricata],
description: (
{i18n.SURICATA_DESCRIPTION_PART1}{' '}
@@ -207,14 +227,14 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.threat_match,
- name: i18n.THREAT_MATCH_NAME,
+ name: eventRendererNames[RowRendererId.threat_match],
description: i18n.THREAT_MATCH_DESCRIPTION,
example: ThreatMatchExample,
searchableDescription: `${i18n.THREAT_MATCH_NAME} ${i18n.THREAT_MATCH_DESCRIPTION}`,
},
{
id: RowRendererId.zeek,
- name: i18n.ZEEK_NAME,
+ name: eventRendererNames[RowRendererId.zeek],
description: (
{i18n.ZEEK_DESCRIPTION_PART1}{' '}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
index fc13680b81be2b..1e6f613999ece3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
@@ -6,7 +6,9 @@
*/
import type React from 'react';
-import { ColumnHeaderOptions } from '../../../../../../common';
+
+import { BrowserFields, ColumnHeaderOptions, RowRenderer } from '../../../../../../common';
+import { Ecs } from '../../../../../../common/ecs';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';
export interface ColumnRenderer {
@@ -29,5 +31,8 @@ export interface ColumnRenderer {
truncate?: boolean;
values: string[] | null | undefined;
linkValues?: string[] | null | undefined;
+ ecsData?: Ecs;
+ rowRenderers?: RowRenderer[];
+ browserFields?: BrowserFields;
}) => React.ReactNode;
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
index aeb40bed26c8ea..3a7a43da2aedc6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
@@ -17,3 +17,4 @@ export const EVENT_URL_FIELD_NAME = 'event.url';
export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name';
export const SIGNAL_STATUS_FIELD_NAME = 'signal.status';
export const AGENT_STATUS_FIELD_NAME = 'agent.status';
+export const REASON_FIELD_NAME = 'signal.reason';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
index 911dcc8cd2e875..11c501f9426f40 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
@@ -16,6 +16,7 @@ import { unknownColumnRenderer } from './unknown_column_renderer';
import { zeekRowRenderer } from './zeek/zeek_row_renderer';
import { systemRowRenderers } from './system/generic_row_renderer';
import { threatMatchRowRenderer } from './cti/threat_match_row_renderer';
+import { reasonColumnRenderer } from './reason_column_renderer';
// The row renderers are order dependent and will return the first renderer
// which returns true from its isInstance call. The bottom renderers which
@@ -34,6 +35,7 @@ export const defaultRowRenderers: RowRenderer[] = [
];
export const columnRenderers: ColumnRenderer[] = [
+ reasonColumnRenderer,
plainColumnRenderer,
emptyColumnRenderer,
unknownColumnRenderer,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx
new file mode 100644
index 00000000000000..addb991af58d72
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx
@@ -0,0 +1,148 @@
+/*
+ * 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 { mockTimelineData } from '../../../../../common/mock';
+import { defaultColumnHeaderType } from '../column_headers/default_headers';
+import { REASON_FIELD_NAME } from './constants';
+import { reasonColumnRenderer } from './reason_column_renderer';
+import { plainColumnRenderer } from './plain_column_renderer';
+
+import {
+ BrowserFields,
+ ColumnHeaderOptions,
+ RowRenderer,
+ RowRendererId,
+} from '../../../../../../common';
+import { fireEvent, render } from '@testing-library/react';
+import { TestProviders } from '../../../../../../../timelines/public/mock';
+import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../../timelines/public/components';
+import { cloneDeep } from 'lodash';
+jest.mock('./plain_column_renderer');
+
+jest.mock('../../../../../common/lib/kibana', () => {
+ const originalModule = jest.requireActual('../../../../../common/lib/kibana');
+ return {
+ ...originalModule,
+ useKibana: () => ({
+ services: {
+ timelines: {
+ getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
+ },
+ },
+ }),
+ };
+});
+
+jest.mock('../../../../../common/components/link_to', () => {
+ const original = jest.requireActual('../../../../../common/components/link_to');
+ return {
+ ...original,
+ useFormatUrl: () => ({
+ formatUrl: () => '',
+ }),
+ };
+});
+
+const invalidEcs = cloneDeep(mockTimelineData[0].ecs);
+const validEcs = cloneDeep(mockTimelineData[28].ecs);
+
+const field: ColumnHeaderOptions = {
+ id: 'test-field-id',
+ columnHeaderType: defaultColumnHeaderType,
+};
+
+const rowRenderers: RowRenderer[] = [
+ {
+ id: RowRendererId.alerts,
+ isInstance: (ecs) => ecs === validEcs,
+ // eslint-disable-next-line react/display-name
+ renderRow: () => ,
+ },
+];
+const browserFields: BrowserFields = {};
+
+const defaultProps = {
+ columnName: REASON_FIELD_NAME,
+ eventId: 'test-event-id',
+ field,
+ timelineId: 'test-timeline-id',
+ values: ['test-value'],
+};
+
+describe('reasonColumnRenderer', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('isIntance', () => {
+ it('returns true when columnName is `signal.reason`', () => {
+ expect(reasonColumnRenderer.isInstance(REASON_FIELD_NAME, [])).toBeTruthy();
+ });
+ });
+
+ describe('renderColumn', () => {
+ it('calls `plainColumnRenderer.renderColumn` when ecsData, rowRenderers or browserFields is empty', () => {
+ reasonColumnRenderer.renderColumn(defaultProps);
+
+ expect(plainColumnRenderer.renderColumn).toBeCalledTimes(1);
+ });
+
+ it("doesn't call `plainColumnRenderer.renderColumn` when ecsData, rowRenderers or browserFields fields are not empty", () => {
+ reasonColumnRenderer.renderColumn({
+ ...defaultProps,
+ ecsData: invalidEcs,
+ rowRenderers,
+ browserFields,
+ });
+
+ expect(plainColumnRenderer.renderColumn).toBeCalledTimes(0);
+ });
+
+ it("doesn't render popover button when getRowRenderer doesn't find a rowRenderer", () => {
+ const renderedColumn = reasonColumnRenderer.renderColumn({
+ ...defaultProps,
+ ecsData: invalidEcs,
+ rowRenderers,
+ browserFields,
+ });
+
+ const wrapper = render({renderedColumn});
+
+ expect(wrapper.queryByTestId('reason-cell-button')).not.toBeInTheDocument();
+ });
+
+ it('render popover button when getRowRenderer finds a rowRenderer', () => {
+ const renderedColumn = reasonColumnRenderer.renderColumn({
+ ...defaultProps,
+ ecsData: validEcs,
+ rowRenderers,
+ browserFields,
+ });
+
+ const wrapper = render({renderedColumn});
+
+ expect(wrapper.queryByTestId('reason-cell-button')).toBeInTheDocument();
+ });
+
+ it('render rowRender inside a popover when reson field button is clicked', () => {
+ const renderedColumn = reasonColumnRenderer.renderColumn({
+ ...defaultProps,
+ ecsData: validEcs,
+ rowRenderers,
+ browserFields,
+ });
+
+ const wrapper = render({renderedColumn});
+
+ fireEvent.click(wrapper.getByTestId('reason-cell-button'));
+
+ expect(wrapper.queryByTestId('test-row-render')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx
new file mode 100644
index 00000000000000..0914c861d00edf
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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 { EuiButtonEmpty, EuiPopover, EuiPopoverTitle } from '@elastic/eui';
+import { isEqual } from 'lodash/fp';
+import React, { useCallback, useMemo, useState } from 'react';
+
+import styled from 'styled-components';
+import { BrowserFields, ColumnHeaderOptions, RowRenderer } from '../../../../../../common';
+import { Ecs } from '../../../../../../common/ecs';
+import { DefaultDraggable } from '../../../../../common/components/draggables';
+import { eventRendererNames } from '../../../row_renderers_browser/catalog';
+import { ColumnRenderer } from './column_renderer';
+import { REASON_FIELD_NAME } from './constants';
+import { getRowRenderer } from './get_row_renderer';
+import { plainColumnRenderer } from './plain_column_renderer';
+import * as i18n from './translations';
+
+export const reasonColumnRenderer: ColumnRenderer = {
+ isInstance: isEqual(REASON_FIELD_NAME),
+
+ renderColumn: ({
+ columnName,
+ eventId,
+ field,
+ isDraggable = true,
+ timelineId,
+ truncate,
+ values,
+ linkValues,
+ ecsData,
+ rowRenderers = [],
+ browserFields,
+ }: {
+ columnName: string;
+ eventId: string;
+ field: ColumnHeaderOptions;
+ isDraggable?: boolean;
+ timelineId: string;
+ truncate?: boolean;
+ values: string[] | undefined | null;
+ linkValues?: string[] | null | undefined;
+
+ ecsData?: Ecs;
+ rowRenderers?: RowRenderer[];
+ browserFields?: BrowserFields;
+ }) =>
+ values != null && ecsData && rowRenderers?.length > 0 && browserFields
+ ? values.map((value, i) => (
+
+ ))
+ : plainColumnRenderer.renderColumn({
+ columnName,
+ eventId,
+ field,
+ isDraggable,
+ timelineId,
+ truncate,
+ values,
+ linkValues,
+ }),
+};
+
+const StyledEuiButtonEmpty = styled(EuiButtonEmpty)`
+ font-weight: ${(props) => props.theme.eui.euiFontWeightRegular};
+`;
+
+const ReasonCell: React.FC<{
+ contextId: string;
+ eventId: string;
+ fieldName: string;
+ isDraggable?: boolean;
+ value: string | number | undefined | null;
+ timelineId: string;
+ ecsData: Ecs;
+ rowRenderers: RowRenderer[];
+ browserFields: BrowserFields;
+}> = ({
+ ecsData,
+ rowRenderers,
+ browserFields,
+ timelineId,
+ value,
+ fieldName,
+ isDraggable,
+ contextId,
+ eventId,
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const rowRenderer = useMemo(() => getRowRenderer(ecsData, rowRenderers), [ecsData, rowRenderers]);
+
+ const rowRender = useMemo(() => {
+ return (
+ rowRenderer &&
+ rowRenderer.renderRow({
+ browserFields,
+ data: ecsData,
+ isDraggable: true,
+ timelineId,
+ })
+ );
+ }, [rowRenderer, browserFields, ecsData, timelineId]);
+
+ const handleTogglePopOver = useCallback(() => setIsOpen(!isOpen), [setIsOpen, isOpen]);
+ const handleClosePopOver = useCallback(() => setIsOpen(false), [setIsOpen]);
+
+ const button = useMemo(
+ () => (
+
+ {value}
+
+ ),
+ [value, handleTogglePopOver]
+ );
+
+ return (
+ <>
+
+ {rowRenderer && rowRender ? (
+
+
+ {i18n.EVENT_RENDERER_POPOVER_TITLE(eventRendererNames[rowRenderer.id] ?? '')}
+
+ {rowRender}
+
+ ) : (
+ value
+ )}
+
+ >
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
index d00148d41f3f64..a703c7afdaf7bc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
@@ -51,3 +51,9 @@ export const EMPTY_STATUS = i18n.translate(
defaultMessage: '-',
}
);
+
+export const EVENT_RENDERER_POPOVER_TITLE = (eventRendererName: string) =>
+ i18n.translate('xpack.securitySolution.event.reason.eventRenderPopoverTitle', {
+ values: { eventRendererName },
+ defaultMessage: 'Event renderer: {eventRendererName} ',
+ });
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx
index d2652ed063fc7c..d45c8103d1cca5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx
@@ -22,6 +22,9 @@ export const DefaultCellRenderer: React.FC = ({
linkValues,
setCellProps,
timelineId,
+ rowRenderers,
+ browserFields,
+ ecsData,
}) => (
<>
{getColumnRenderer(header.id, columnRenderers, data).renderColumn({
@@ -36,6 +39,9 @@ export const DefaultCellRenderer: React.FC = ({
data,
fieldName: header.id,
}),
+ rowRenderers,
+ browserFields,
+ ecsData,
})}
>
);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts
index 25e687050cf882..9d72d72e78b168 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts
@@ -5,7 +5,9 @@
* 2.0.
*/
-export const createRuleMock = () => ({
+import { RuleParams } from '../../schemas/rule_schemas';
+
+export const createRuleMock = (params: Partial) => ({
actions: [],
author: [],
buildingBlockType: undefined,
@@ -49,4 +51,5 @@ export const createRuleMock = () => ({
updatedAt: '2020-01-10T21:11:45.839Z',
updatedBy: 'elastic',
version: 1,
+ ...params,
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
index 0fa2bcc270a167..151b1d8b2fb13f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts
@@ -18,8 +18,12 @@ import { AlertAttributes } from '../../signals/types';
import { createRuleMock } from './rule';
import { listMock } from '../../../../../../lists/server/mocks';
import { ruleRegistryMocks } from '../../../../../../rule_registry/server/mocks';
+import { RuleParams } from '../../schemas/rule_schemas';
-export const createRuleTypeMocks = () => {
+export const createRuleTypeMocks = (
+ ruleType: string = 'query',
+ ruleParams: Partial = {}
+) => {
/* eslint-disable @typescript-eslint/no-explicit-any */
let alertExecutor: (...args: any[]) => Promise;
@@ -43,7 +47,7 @@ export const createRuleTypeMocks = () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.get.mockResolvedValue({
id: 'de2f6a49-28a3-4794-bad7-0e9482e075f8',
- type: 'query',
+ type: ruleType,
references: [],
attributes: {
actions: [],
@@ -57,7 +61,7 @@ export const createRuleTypeMocks = () => {
interval: '30m',
},
throttle: '',
- params: createRuleMock(),
+ params: createRuleMock(ruleParams),
},
} as SavedObject);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/cti.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/cti.ts
new file mode 100644
index 00000000000000..daf54e4f7cf5cf
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/cti.ts
@@ -0,0 +1,154 @@
+/*
+ * 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 const ctiFieldMap = {
+ 'threat.indicator': {
+ type: 'nested',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.as.number': {
+ type: 'long',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.as.organization.name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.confidence': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.dataset': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.description': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.domain': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.email.address': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.first_seen': {
+ type: 'date',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.city_name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.continent_name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.country_iso_code': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.country_name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.location': {
+ type: 'geo_point',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.region_iso_code': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.geo.region_name': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.ip': {
+ type: 'ip',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.last_seen': {
+ type: 'date',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.marking.tlp': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.matched.atomic': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.matched.field': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.matched.type': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.module': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.port': {
+ type: 'long',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.provider': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.scanner_stats': {
+ type: 'long',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.sightings': {
+ type: 'long',
+ array: false,
+ required: false,
+ },
+ 'threat.indicator.type': {
+ type: 'keyword',
+ array: false,
+ required: false,
+ },
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts
index 1b1fe48be00e00..75252cc3d47aed 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts
@@ -6,3 +6,4 @@
*/
export { createQueryAlertType } from './query/create_query_alert_type';
+export { createIndicatorMatchAlertType } from './indicator_match/create_indicator_match_alert_type';
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts
new file mode 100644
index 00000000000000..50aff001913965
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts
@@ -0,0 +1,260 @@
+/*
+ * 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 { v4 } from 'uuid';
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
+
+import { allowedExperimentalValues } from '../../../../../common/experimental_features';
+import { createRuleTypeMocks } from '../__mocks__/rule_type';
+import { createIndicatorMatchAlertType } from './create_indicator_match_alert_type';
+import { sampleDocNoSortId } from '../../signals/__mocks__/es_results';
+import { CountResponse } from 'kibana/server';
+import { RuleParams } from '../../schemas/rule_schemas';
+
+jest.mock('../utils/get_list_client', () => ({
+ getListClient: jest.fn().mockReturnValue({
+ listClient: {
+ getListItemIndex: jest.fn(),
+ },
+ exceptionsClient: jest.fn(),
+ }),
+}));
+
+jest.mock('../../signals/rule_status_service', () => ({
+ ruleStatusServiceFactory: () => ({
+ goingToRun: jest.fn(),
+ success: jest.fn(),
+ partialFailure: jest.fn(),
+ error: jest.fn(),
+ }),
+}));
+
+describe('Indicator Match Alerts', () => {
+ const params: Partial = {
+ from: 'now-1m',
+ index: ['*'],
+ threatIndex: ['filebeat-*'],
+ threatLanguage: 'kuery',
+ threatMapping: [
+ {
+ entries: [
+ {
+ field: 'file.hash.md5',
+ type: 'mapping',
+ value: 'threatintel.indicator.file.hash.md5',
+ },
+ ],
+ },
+ ],
+ threatQuery: '*:*',
+ to: 'now',
+ type: 'threat_match',
+ };
+
+ it('does not send an alert when no events found', async () => {
+ const { services, dependencies, executor } = createRuleTypeMocks('threat_match', params);
+ const indicatorMatchAlertType = createIndicatorMatchAlertType({
+ experimentalFeatures: allowedExperimentalValues,
+ indexAlias: 'alerts.security-alerts',
+ lists: dependencies.lists,
+ logger: dependencies.logger,
+ mergeStrategy: 'allFields',
+ ruleDataClient: dependencies.ruleDataClient,
+ ruleDataService: dependencies.ruleDataService,
+ version: '1.0.0',
+ });
+
+ dependencies.alerting.registerType(indicatorMatchAlertType);
+
+ services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({
+ hits: {
+ hits: [],
+ sequences: [],
+ events: [],
+ total: {
+ relation: 'eq',
+ value: 0,
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ skipped: 0,
+ successful: 1,
+ total: 1,
+ },
+ })
+ );
+
+ await executor({ params });
+ expect(dependencies.ruleDataClient.getWriter).not.toBeCalled();
+ });
+
+ it('does not send an alert when no enrichments are found', async () => {
+ const { services, dependencies, executor } = createRuleTypeMocks('threat_match', params);
+ const indicatorMatchAlertType = createIndicatorMatchAlertType({
+ experimentalFeatures: allowedExperimentalValues,
+ indexAlias: 'alerts.security-alerts',
+ lists: dependencies.lists,
+ logger: dependencies.logger,
+ mergeStrategy: 'allFields',
+ ruleDataClient: dependencies.ruleDataClient,
+ ruleDataService: dependencies.ruleDataService,
+ version: '1.0.0',
+ });
+
+ dependencies.alerting.registerType(indicatorMatchAlertType);
+
+ services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({
+ hits: {
+ hits: [sampleDocNoSortId(v4()), sampleDocNoSortId(v4()), sampleDocNoSortId(v4())],
+ total: {
+ relation: 'eq',
+ value: 3,
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ skipped: 0,
+ successful: 1,
+ total: 1,
+ },
+ })
+ );
+
+ await executor({ params });
+ expect(dependencies.ruleDataClient.getWriter).not.toBeCalled();
+ });
+
+ it('sends an alert when enrichments are found', async () => {
+ const { services, dependencies, executor } = createRuleTypeMocks('threat_match', params);
+ const indicatorMatchAlertType = createIndicatorMatchAlertType({
+ experimentalFeatures: allowedExperimentalValues,
+ indexAlias: 'alerts.security-alerts',
+ lists: dependencies.lists,
+ logger: dependencies.logger,
+ mergeStrategy: 'allFields',
+ ruleDataClient: dependencies.ruleDataClient,
+ ruleDataService: dependencies.ruleDataService,
+ version: '1.0.0',
+ });
+
+ dependencies.alerting.registerType(indicatorMatchAlertType);
+
+ // threat list count
+ services.scopedClusterClient.asCurrentUser.count.mockReturnValue(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({ count: 1 } as CountResponse)
+ );
+
+ services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({
+ hits: {
+ hits: [
+ {
+ ...sampleDocNoSortId(v4()),
+ _source: {
+ ...sampleDocNoSortId(v4())._source,
+ 'threatintel.indicator.file.hash.md5': 'a1b2c3',
+ },
+ fields: {
+ ...sampleDocNoSortId(v4()).fields,
+ 'threatintel.indicator.file.hash.md5': ['a1b2c3'],
+ },
+ },
+ ],
+ total: {
+ relation: 'eq',
+ value: 1,
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ skipped: 0,
+ successful: 1,
+ total: 1,
+ },
+ })
+ );
+
+ services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({
+ hits: {
+ hits: [
+ {
+ ...sampleDocNoSortId(v4()),
+ _source: {
+ ...sampleDocNoSortId(v4())._source,
+ 'file.hash.md5': 'a1b2c3',
+ },
+ fields: {
+ ...sampleDocNoSortId(v4()).fields,
+ 'file.hash.md5': ['a1b2c3'],
+ },
+ },
+ ],
+ total: {
+ relation: 'eq',
+ value: 1,
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ skipped: 0,
+ successful: 1,
+ total: 1,
+ },
+ })
+ );
+
+ services.scopedClusterClient.asCurrentUser.search.mockReturnValueOnce(
+ elasticsearchClientMock.createSuccessTransportRequestPromise({
+ hits: {
+ hits: [
+ {
+ ...sampleDocNoSortId(v4()),
+ _source: {
+ ...sampleDocNoSortId(v4())._source,
+ 'file.hash.md5': 'a1b2c3',
+ },
+ fields: {
+ ...sampleDocNoSortId(v4()).fields,
+ 'file.hash.md5': ['a1b2c3'],
+ },
+ },
+ ],
+ total: {
+ relation: 'eq',
+ value: 1,
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ skipped: 0,
+ successful: 1,
+ total: 1,
+ },
+ })
+ );
+
+ await executor({ params });
+
+ expect(dependencies.ruleDataClient.getWriter).toBeCalled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts
new file mode 100644
index 00000000000000..61342981479aed
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts
@@ -0,0 +1,99 @@
+/*
+ * 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 { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
+import { PersistenceServices } from '../../../../../../rule_registry/server';
+import { INDICATOR_ALERT_TYPE_ID } from '../../../../../common/constants';
+import { threatRuleParams, ThreatRuleParams } from '../../schemas/rule_schemas';
+import { threatMatchExecutor } from '../../signals/executors/threat_match';
+import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
+import { CreateRuleOptions } from '../types';
+
+export const createIndicatorMatchAlertType = (createOptions: CreateRuleOptions) => {
+ const {
+ experimentalFeatures,
+ indexAlias,
+ lists,
+ logger,
+ mergeStrategy,
+ ruleDataClient,
+ version,
+ ruleDataService,
+ } = createOptions;
+ const createSecurityRuleType = createSecurityRuleTypeFactory({
+ indexAlias,
+ lists,
+ logger,
+ mergeStrategy,
+ ruleDataClient,
+ ruleDataService,
+ });
+ return createSecurityRuleType({
+ id: INDICATOR_ALERT_TYPE_ID,
+ name: 'Indicator Match Rule',
+ validate: {
+ params: {
+ validate: (object: unknown) => {
+ const [validated, errors] = validateNonExact(object, threatRuleParams);
+ if (errors != null) {
+ throw new Error(errors);
+ }
+ if (validated == null) {
+ throw new Error('Validation of rule params failed');
+ }
+ return validated;
+ },
+ },
+ },
+ actionGroups: [
+ {
+ id: 'default',
+ name: 'Default',
+ },
+ ],
+ defaultActionGroupId: 'default',
+ actionVariables: {
+ context: [{ name: 'server', description: 'the server' }],
+ },
+ minimumLicenseRequired: 'basic',
+ isExportable: false,
+ producer: 'security-solution',
+ async executor(execOptions) {
+ const {
+ runOpts: {
+ buildRuleMessage,
+ bulkCreate,
+ exceptionItems,
+ listClient,
+ rule,
+ searchAfterSize,
+ tuple,
+ wrapHits,
+ },
+ services,
+ state,
+ } = execOptions;
+
+ const result = await threatMatchExecutor({
+ buildRuleMessage,
+ bulkCreate,
+ exceptionItems,
+ experimentalFeatures,
+ eventsTelemetry: undefined,
+ listClient,
+ logger,
+ rule,
+ searchAfterSize,
+ services,
+ tuple,
+ version,
+ wrapHits,
+ });
+ return { ...result, state };
+ },
+ });
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts
index c487d8c93119dd..b66e76f598bf4d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts
@@ -5,28 +5,15 @@
* 2.0.
*/
-import { Logger } from '@kbn/logging';
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
-import { PersistenceServices, RuleDataClient } from '../../../../../../rule_registry/server';
+import { PersistenceServices } from '../../../../../../rule_registry/server';
import { QUERY_ALERT_TYPE_ID } from '../../../../../common/constants';
-import { ExperimentalFeatures } from '../../../../../common/experimental_features';
-import { ConfigType } from '../../../../config';
-import { SetupPlugins } from '../../../../plugin';
-import { IRuleDataPluginService } from '../../rule_execution_log/types';
import { queryRuleParams, QueryRuleParams } from '../../schemas/rule_schemas';
import { queryExecutor } from '../../signals/executors/query';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
+import { CreateRuleOptions } from '../types';
-export const createQueryAlertType = (createOptions: {
- experimentalFeatures: ExperimentalFeatures;
- indexAlias: string;
- lists: SetupPlugins['lists'];
- logger: Logger;
- mergeStrategy: ConfigType['alertMergeStrategy'];
- ruleDataClient: RuleDataClient;
- version: string;
- ruleDataService: IRuleDataPluginService;
-}) => {
+export const createQueryAlertType = (createOptions: CreateRuleOptions) => {
const {
experimentalFeatures,
indexAlias,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_indicator_match.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_indicator_match.sh
new file mode 100644
index 00000000000000..f50aac30a69c53
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_indicator_match.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+#
+# 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.
+#
+
+# to see alerts with this script
+# 1. enable filebeat threatintel module and run filebeat
+# 2. using Kibana DevTools, create `test-index` with the following mappings: @timestamp:date, file.hash.md5:text
+# 3. run this script
+# 4. using Kibana DevTools, post to test-index an existing file.hash.md5 value with a current timestamp
+# 5. alert should be generated and can be viewed on the Alerts table
+
+curl -X POST ${KIBANA_URL}${SPACE_URL}/api/alerts/alert \
+ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
+ -H 'kbn-xsrf: true' \
+ -H 'Content-Type: application/json' \
+ --verbose \
+ -d '
+{
+ "params":{
+ "author": [],
+ "description": "Indicator Match Rule",
+ "exceptionsList": [],
+ "falsePositives": [],
+ "from": "now-300s",
+ "query": "*:*",
+ "immutable": false,
+ "index": ["test-*"],
+ "language": "kuery",
+ "maxSignals": 10,
+ "outputIndex": "",
+ "references": [],
+ "riskScore": 21,
+ "riskScoreMapping": [],
+ "ruleId": "81dec1ba-b779-469c-9667-6b0e865fb86c",
+ "severity": "low",
+ "severityMapping": [],
+ "threat": [],
+ "threatQuery": "*:*",
+ "threatMapping": [
+ {
+ "entries":[
+ {
+ "field":"file.hash.md5",
+ "type":"mapping",
+ "value":"threatintel.indicator.file.hash.md5"
+ }
+ ]
+ }
+ ],
+ "threatLanguage": "kuery",
+ "threatIndex": ["filebeat-*"],
+ "to": "now",
+ "type": "threat_match",
+ "version": 1
+ },
+ "consumer":"alerts",
+ "alertTypeId":"siem.indicatorRule",
+ "schedule":{
+ "interval":"1m"
+ },
+ "actions":[],
+ "tags":[
+ "indicator match",
+ "persistence"
+ ],
+ "notifyWhen":"onActionGroupChange",
+ "name":"Basic Indicator Match Rule"
+}'
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
index 20fb7213086008..f0f11f470bc6cc 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
@@ -33,6 +33,7 @@ import { RuleParams } from '../schemas/rule_schemas';
import { BuildRuleMessage } from '../signals/rule_messages';
import { AlertAttributes, BulkCreate, WrapHits } from '../signals/types';
import { AlertsFieldMap, RulesFieldMap } from './field_maps';
+import { ExperimentalFeatures } from '../../../../common/experimental_features';
export interface SecurityAlertTypeReturnValue {
bulkCreateTimes: string[];
@@ -118,3 +119,14 @@ export type RACAlert = Exclude<
export type RACSourceHit = SearchHit;
export type WrappedRACAlert = BaseHit;
+
+export interface CreateRuleOptions {
+ experimentalFeatures: ExperimentalFeatures;
+ indexAlias: string;
+ lists: SetupPlugins['lists'];
+ logger: Logger;
+ mergeStrategy: ConfigType['alertMergeStrategy'];
+ ruleDataClient: RuleDataClient;
+ version: string;
+ ruleDataService: IRuleDataPluginService;
+}
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index e67c9fbfd8327d..d19c799295cfb9 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -66,6 +66,7 @@ import {
NOTIFICATIONS_ID,
QUERY_ALERT_TYPE_ID,
DEFAULT_SPACE_ID,
+ INDICATOR_ALERT_TYPE_ID,
} from '../common/constants';
import { registerEndpointRoutes } from './endpoint/routes/metadata';
import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency';
@@ -94,6 +95,9 @@ import { rulesFieldMap } from './lib/detection_engine/rule_types/field_maps/rule
import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client';
import { getKibanaPrivilegesFeaturePrivileges } from './features';
import { EndpointMetadataService } from './endpoint/services/metadata';
+import { createIndicatorMatchAlertType } from './lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type';
+import { CreateRuleOptions } from './lib/detection_engine/rule_types/types';
+import { ctiFieldMap } from './lib/detection_engine/rule_types/field_maps/cti';
export interface SetupPlugins {
alerting: AlertingSetup;
@@ -231,7 +235,10 @@ export class Plugin implements IPlugin void;
+ ecsData?: Ecs;
+ rowRenderers?: RowRenderer[];
+ browserFields?: BrowserFields;
};
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
index cc94f901446a70..5fba7cff55e5c3 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx
@@ -526,9 +526,12 @@ export const BodyComponent = React.memo(
rowIndex,
setCellProps,
timelineId: tabType != null ? `${id}-${tabType}` : id,
+ ecsData: data[rowIndex].ecs,
+ browserFields,
+ rowRenderers,
});
},
- [columnHeaders, data, id, renderCellValue, tabType, theme]
+ [columnHeaders, data, id, renderCellValue, tabType, theme, browserFields, rowRenderers]
);
return (
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c2fd76b0b73af9..d1ce8e1e429658 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -12127,30 +12127,16 @@
"xpack.infra.metrics.alertFlyout.aggregationText.sum": "合計",
"xpack.infra.metrics.alertFlyout.alertDescription": "メトリックアグリゲーションがしきい値を超えたときにアラートを発行します。",
"xpack.infra.metrics.alertFlyout.alertOnNoData": "データがない場合に通知する",
- "xpack.infra.metrics.alertFlyout.alertPreviewError": "このアラート条件をプレビューするときにエラーが発生しました",
- "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "しばらくたってから再試行するか、詳細を確認してください。",
- "xpack.infra.metrics.alertFlyout.alertPreviewErrorResult": "一部のデータを評価するときにエラーが発生しました。",
- "xpack.infra.metrics.alertFlyout.alertPreviewGroupBy": "{groups}全体",
- "xpack.infra.metrics.alertFlyout.alertPreviewLookback": "前回の{lookback}",
- "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResult": "データなしの{wereWas} {boldedResultsNumber}がありました。",
- "xpack.infra.metrics.alertFlyout.alertPreviewOnlyOnStatusChange": "ステータス変更時のみ",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultInstances": "このアラートの条件を満たした{wereWas} {firedTimes}がありました",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultText": "{instanceCount} {groupByWithConditionalTrailingSpace}{lookbackText}.",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultWithSeverityLevels": "このアラートの{boldCritical}条件を満たした{wereWas} {criticalInstances}と、{boldWarning}条件を満たした{warningInstances}",
- "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotifications": "結果として、このアラートは、「{alertThrottle}」に関して選択した[通知]設定に基づいて{notifications}を送信しました。",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpText": "アラートトリガーの範囲を、特定のノードの影響を受ける異常に制限します。",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample": "例:「my-node-1」または「my-node-*」",
"xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder": "すべて",
"xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage": "メモリー使用状況",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn": "内向きのネットワーク",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut": "外向きのネットワーク",
- "xpack.infra.metrics.alertFlyout.boldCritical": "致命的",
- "xpack.infra.metrics.alertFlyout.boldWarning": "警告",
"xpack.infra.metrics.alertFlyout.conditions": "条件",
"xpack.infra.metrics.alertFlyout.createAlertPerHelpText": "すべての一意の値についてアラートを作成します。例:「host.id」または「cloud.region」。",
"xpack.infra.metrics.alertFlyout.createAlertPerText": "次の単位でアラートを作成 (任意) ",
"xpack.infra.metrics.alertFlyout.criticalThreshold": "アラート",
- "xpack.infra.metrics.alertFlyout.dayLabel": "日",
"xpack.infra.metrics.alertFlyout.error.aggregationRequired": "集約が必要です。",
"xpack.infra.metrics.alertFlyout.error.customMetricFieldRequired": "フィールドが必要です。",
"xpack.infra.metrics.alertFlyout.error.metricRequired": "メトリックが必要です。",
@@ -12158,7 +12144,6 @@
"xpack.infra.metrics.alertFlyout.error.thresholdRequired": "しきい値が必要です。",
"xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired": "しきい値には有効な数値を含める必要があります。",
"xpack.infra.metrics.alertFlyout.error.timeRequred": "ページサイズが必要です。",
- "xpack.infra.metrics.alertFlyout.errorDetails": "詳細",
"xpack.infra.metrics.alertFlyout.expandRowLabel": "行を展開します。",
"xpack.infra.metrics.alertFlyout.expression.for.descriptionLabel": "対象",
"xpack.infra.metrics.alertFlyout.expression.for.popoverTitle": "ノードのタイプ",
@@ -12174,26 +12159,13 @@
"xpack.infra.metrics.alertFlyout.filterByNodeLabel": "ノードでフィルタリング",
"xpack.infra.metrics.alertFlyout.filterHelpText": "KQL式を使用して、アラートトリガーの範囲を制限します。",
"xpack.infra.metrics.alertFlyout.filterLabel": "フィルター (任意) ",
- "xpack.infra.metrics.alertFlyout.hourLabel": "時間",
- "xpack.infra.metrics.alertFlyout.lastDayLabel": "昨日",
- "xpack.infra.metrics.alertFlyout.lastHourLabel": "過去1時間",
- "xpack.infra.metrics.alertFlyout.lastMonthLabel": "先月",
- "xpack.infra.metrics.alertFlyout.lastWeekLabel": "先週",
- "xpack.infra.metrics.alertFlyout.monthLabel": "月",
"xpack.infra.metrics.alertFlyout.noDataHelpText": "有効にすると、メトリックが想定された期間内にデータを報告しない場合、またはアラートがElasticsearchをクエリできない場合に、アクションをトリガーします",
"xpack.infra.metrics.alertFlyout.ofExpression.helpTextDetail": "メトリックが見つからない場合は、{documentationLink}。",
"xpack.infra.metrics.alertFlyout.ofExpression.popoverLinkLabel": "データの追加方法",
"xpack.infra.metrics.alertFlyout.outsideRangeLabel": "is not between",
- "xpack.infra.metrics.alertFlyout.previewIntervalTooShortDescription": "選択するプレビュー長を長くするか、{checkEvery}フィールドの時間を増やしてください。",
- "xpack.infra.metrics.alertFlyout.previewIntervalTooShortTitle": "データが不十分です",
- "xpack.infra.metrics.alertFlyout.previewLabel": "プレビュー",
"xpack.infra.metrics.alertFlyout.removeCondition": "条件を削除",
"xpack.infra.metrics.alertFlyout.removeWarningThreshold": "warningThresholdを削除",
- "xpack.infra.metrics.alertFlyout.testAlertCondition": "アラート条件のテスト",
- "xpack.infra.metrics.alertFlyout.tooManyBucketsErrorDescription": "選択するプレビュー長を短くするか、{forTheLast}フィールドの時間を増やしてください。",
- "xpack.infra.metrics.alertFlyout.tooManyBucketsErrorTitle": "データが多すぎます (>{maxBuckets}結果) ",
"xpack.infra.metrics.alertFlyout.warningThreshold": "警告",
- "xpack.infra.metrics.alertFlyout.weekLabel": "週",
"xpack.infra.metrics.alerting.alertStateActionVariableDescription": "現在のアラートの状態",
"xpack.infra.metrics.alerting.anomaly.defaultActionMessage": "\\{\\{alertName\\}\\}は\\{\\{context.alertState\\}\\}の状態です\n\n\\{\\{context.metric\\}\\}は\\{\\{context.timestamp\\}\\}で標準を超える\\{\\{context.summary\\}\\}でした\n\n標準の値:\\{\\{context.typical\\}\\}\n実際の値:\\{\\{context.actual\\}\\}\n",
"xpack.infra.metrics.alerting.anomaly.fired": "実行",
@@ -12460,7 +12432,6 @@
"xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。",
"xpack.infra.registerFeatures.logsTitle": "ログ",
"xpack.infra.sampleDataLinkLabel": "ログ",
- "xpack.infra.savedView.currentView": "現在のビュー",
"xpack.infra.savedView.defaultViewNameHosts": "デフォルトビュー",
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューはすでに存在します。",
"xpack.infra.savedView.errorOnCreate.title": "ビューの保存中にエラーが発生しました。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 2e7bf5d19687c6..43b1f23233db18 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -12455,33 +12455,16 @@
"xpack.infra.metrics.alertFlyout.aggregationText.sum": "求和",
"xpack.infra.metrics.alertFlyout.alertDescription": "当指标聚合超过阈值时告警。",
"xpack.infra.metrics.alertFlyout.alertOnNoData": "没数据时提醒我",
- "xpack.infra.metrics.alertFlyout.alertPreviewError": "尝试预览此告警条件时发生错误",
- "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "请稍后重试或查看详情了解更多信息。",
- "xpack.infra.metrics.alertFlyout.alertPreviewErrorResult": "尝试评估部分数据时发生错误。",
- "xpack.infra.metrics.alertFlyout.alertPreviewGroupBy": "跨 {groups}",
- "xpack.infra.metrics.alertFlyout.alertPreviewGroups": "{numberOfGroups, plural,other {# 个 {groupName}}}",
- "xpack.infra.metrics.alertFlyout.alertPreviewLookback": "在过去 {lookback}",
- "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResult": "有 {wereWas}{boldedResultsNumber}。",
- "xpack.infra.metrics.alertFlyout.alertPreviewNoDataResultNumber": "{noData, plural, other {# 个无数据结果}}",
- "xpack.infra.metrics.alertFlyout.alertPreviewOnlyOnStatusChange": "仅在状态更改时",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultInstances": "有 {wereWas}{firedTimes} 次满足此告警的条件",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultText": "{instanceCount} {groupByWithConditionalTrailingSpace}{lookbackText}。",
- "xpack.infra.metrics.alertFlyout.alertPreviewResultWithSeverityLevels": "有 {wereWas}{criticalInstances}满足此告警的{boldCritical}条件,{warningInstances}满足{boldWarning}条件",
- "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotifications": "因此,此告警将根据“{alertThrottle}”的选定“通知”设置发送{notifications}。",
- "xpack.infra.metrics.alertFlyout.alertPreviewTotalNotificationsNumber": "{notifs, plural, other {# 个通知}}",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpText": "将告警触发的范围限定在特定节点影响的异常。",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample": "例如:“my-node-1”或“my-node-*”",
"xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder": "所有内容",
"xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage": "内存使用",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn": "网络传入",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut": "网络传出",
- "xpack.infra.metrics.alertFlyout.boldCritical": "紧急",
- "xpack.infra.metrics.alertFlyout.boldWarning": "警告",
"xpack.infra.metrics.alertFlyout.conditions": "条件",
"xpack.infra.metrics.alertFlyout.createAlertPerHelpText": "为每个唯一值创建告警。例如:“host.id”或“cloud.region”。",
"xpack.infra.metrics.alertFlyout.createAlertPerText": "创建告警时间间隔(可选)",
"xpack.infra.metrics.alertFlyout.criticalThreshold": "告警",
- "xpack.infra.metrics.alertFlyout.dayLabel": "天",
"xpack.infra.metrics.alertFlyout.error.aggregationRequired": "“聚合”必填。",
"xpack.infra.metrics.alertFlyout.error.customMetricFieldRequired": "“字段”必填。",
"xpack.infra.metrics.alertFlyout.error.metricRequired": "“指标”必填。",
@@ -12489,7 +12472,6 @@
"xpack.infra.metrics.alertFlyout.error.thresholdRequired": "“阈值”必填。",
"xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired": "阈值必须包含有效数字。",
"xpack.infra.metrics.alertFlyout.error.timeRequred": "“时间大小”必填。",
- "xpack.infra.metrics.alertFlyout.errorDetails": "详情",
"xpack.infra.metrics.alertFlyout.expandRowLabel": "展开行。",
"xpack.infra.metrics.alertFlyout.expression.for.descriptionLabel": "对于",
"xpack.infra.metrics.alertFlyout.expression.for.popoverTitle": "节点类型",
@@ -12505,28 +12487,13 @@
"xpack.infra.metrics.alertFlyout.filterByNodeLabel": "按节点筛选",
"xpack.infra.metrics.alertFlyout.filterHelpText": "使用 KQL 表达式限制告警触发的范围。",
"xpack.infra.metrics.alertFlyout.filterLabel": "筛选(可选)",
- "xpack.infra.metrics.alertFlyout.firedTimes": "{fired, plural, other {# 个实例}}",
- "xpack.infra.metrics.alertFlyout.hourLabel": "小时",
- "xpack.infra.metrics.alertFlyout.lastDayLabel": "昨天",
- "xpack.infra.metrics.alertFlyout.lastHourLabel": "上一小时",
- "xpack.infra.metrics.alertFlyout.lastMonthLabel": "上个月",
- "xpack.infra.metrics.alertFlyout.lastWeekLabel": "上周",
- "xpack.infra.metrics.alertFlyout.monthLabel": "个月",
"xpack.infra.metrics.alertFlyout.noDataHelpText": "启用此选项可在指标在预期的时间段中未报告任何数据时或告警无法查询 Elasticsearch 时触发操作",
"xpack.infra.metrics.alertFlyout.ofExpression.helpTextDetail": "找不到指标?{documentationLink}。",
"xpack.infra.metrics.alertFlyout.ofExpression.popoverLinkLabel": "了解如何添加更多数据",
"xpack.infra.metrics.alertFlyout.outsideRangeLabel": "不介于",
- "xpack.infra.metrics.alertFlyout.previewIntervalTooShortDescription": "尝试选择较长的预览长度或在“{checkEvery}”字段增大时间量。",
- "xpack.infra.metrics.alertFlyout.previewIntervalTooShortTitle": "没有足够的数据",
- "xpack.infra.metrics.alertFlyout.previewLabel": "预览",
"xpack.infra.metrics.alertFlyout.removeCondition": "删除条件",
"xpack.infra.metrics.alertFlyout.removeWarningThreshold": "移除警告阈值",
- "xpack.infra.metrics.alertFlyout.testAlertCondition": "测试告警条件",
- "xpack.infra.metrics.alertFlyout.tooManyBucketsErrorDescription": "尝试选择较短的预览长度或在“{forTheLast}”字段中增大时间量。",
- "xpack.infra.metrics.alertFlyout.tooManyBucketsErrorTitle": "数据过多(>{maxBuckets} 个结果)",
"xpack.infra.metrics.alertFlyout.warningThreshold": "警告",
- "xpack.infra.metrics.alertFlyout.weekLabel": "周",
- "xpack.infra.metrics.alertFlyout.wereWas": "{plurality, plural, other {}}",
"xpack.infra.metrics.alerting.alertStateActionVariableDescription": "告警的当前状态",
"xpack.infra.metrics.alerting.anomaly.defaultActionMessage": "\\{\\{alertName\\}\\} 处于 \\{\\{context.alertState\\}\\} 状态\n\n\\{\\{context.metric\\}\\} 在 \\{\\{context.timestamp\\}\\}比正常\\{\\{context.summary\\}\\}\n\n典型值:\\{\\{context.typical\\}\\}\n实际值:\\{\\{context.actual\\}\\}\n",
"xpack.infra.metrics.alerting.anomaly.fired": "已触发",
@@ -12793,7 +12760,6 @@
"xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。",
"xpack.infra.registerFeatures.logsTitle": "日志",
"xpack.infra.sampleDataLinkLabel": "日志",
- "xpack.infra.savedView.currentView": "当前视图",
"xpack.infra.savedView.defaultViewNameHosts": "默认视图",
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。",
"xpack.infra.savedView.errorOnCreate.title": "保存视图时出错。",
diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts
index b85364bb84766d..daddb9b24fe032 100644
--- a/x-pack/test/accessibility/apps/spaces.ts
+++ b/x-pack/test/accessibility/apps/spaces.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-// a11y tests for spaces, space selection and spacce creation and feature controls
+// a11y tests for spaces, space selection and space creation and feature controls
import { FtrProviderContext } from '../ftr_provider_context';
@@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
// EUI issue - https://github.com/elastic/eui/issues/3999
- it.skip('a11y test for color picker', async () => {
+ it('a11y test for color picker', async () => {
await PageObjects.spaceSelector.clickColorPicker();
await a11y.testAppSnapshot();
await browser.pressKeys(browser.keys.ESCAPE);
diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts
index c16219bae89ba2..e344f86f89fe8e 100644
--- a/x-pack/test/functional/apps/infra/home_page.ts
+++ b/x-pack/test/functional/apps/infra/home_page.ts
@@ -16,7 +16,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'infraHome', 'infraSavedViews']);
// Failing: See https://github.com/elastic/kibana/issues/106650
- describe.skip('Home page', function () {
+ describe('Home page', function () {
this.tags('includeFirefox');
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana');
diff --git a/x-pack/test/functional/apps/infra/metrics_explorer.ts b/x-pack/test/functional/apps/infra/metrics_explorer.ts
index 5a8d7da628259f..90a875a07f8a79 100644
--- a/x-pack/test/functional/apps/infra/metrics_explorer.ts
+++ b/x-pack/test/functional/apps/infra/metrics_explorer.ts
@@ -88,7 +88,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
// FLAKY: https://github.com/elastic/kibana/issues/106651
- describe.skip('Saved Views', () => {
+ describe('Saved Views', () => {
before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'));
after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'));
describe('save functionality', () => {
diff --git a/x-pack/test/functional/apps/management/create_index_pattern_wizard.js b/x-pack/test/functional/apps/management/create_index_pattern_wizard.js
index 445e340d236e69..9061817bbce790 100644
--- a/x-pack/test/functional/apps/management/create_index_pattern_wizard.js
+++ b/x-pack/test/functional/apps/management/create_index_pattern_wizard.js
@@ -8,14 +8,20 @@
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const es = getService('es');
+ const security = getService('security');
const PageObjects = getPageObjects(['settings', 'common']);
+ const soInfo = getService('savedObjectInfo');
+ const log = getService('log');
describe('"Create Index Pattern" wizard', function () {
before(async function () {
- // delete .kibana index and then wait for Kibana to re-create it
+ await soInfo.logSoTypes(log);
+ await security.testUser.setRoles([
+ 'global_index_pattern_management_all',
+ 'test_logs_data_reader',
+ ]);
await kibanaServer.uiSettings.replace({});
await PageObjects.settings.navigateTo();
- await PageObjects.settings.clickKibanaIndexPatterns();
});
describe('data streams', () => {
@@ -43,13 +49,19 @@ export default function ({ getService, getPageObjects }) {
method: 'PUT',
});
+ await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.createIndexPattern('test_data_stream');
+ });
+ });
- await es.transport.request({
- path: '/_data_stream/test_data_stream',
- method: 'DELETE',
- });
+ after(async () => {
+ await kibanaServer.savedObjects.clean({ types: ['index-pattern'] });
+ await es.transport.request({
+ path: '/_data_stream/test_data_stream',
+ method: 'DELETE',
});
+ await security.testUser.restoreDefaults();
+ await soInfo.logSoTypes(log);
});
});
}
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 06e58638922d4a..704ce819b5b382 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -384,6 +384,17 @@ export default async function ({ readConfigFile }) {
},
},
+ test_logs_data_reader: {
+ elasticsearch: {
+ indices: [
+ {
+ names: ['test_data_stream'],
+ privileges: ['read', 'view_index_metadata'],
+ },
+ ],
+ },
+ },
+
geoall_data_writer: {
elasticsearch: {
indices: [
diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts
index 31b528d4ec1532..ef91b3b5fcb3c2 100644
--- a/x-pack/test/functional/page_objects/infra_saved_views.ts
+++ b/x-pack/test/functional/page_objects/infra_saved_views.ts
@@ -74,13 +74,14 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) {
},
async ensureViewIsLoaded(name: string) {
- const subject = await testSubjects.find('savedViews-currentViewName');
+ const subject = await testSubjects.find('savedViews-openPopover');
expect(await subject.getVisibleText()).to.be(name);
},
async ensureViewIsLoadable(name: string) {
const subjects = await testSubjects.getVisibleTextAll('savedViews-loadList');
- expect(subjects.some((s) => s.split('\n').includes(name))).to.be(true);
+ const includesName = subjects.some((s) => s.includes(name));
+ expect(includesName).to.be(true);
},
async closeSavedViewsLoadModal() {