From 10c499543a6fe9faaff8462f4e0a75db361f32b8 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 24 Aug 2020 13:56:54 +0100 Subject: [PATCH 01/39] [ML] Fixing new population job wizard with saved search (#75731) (#75737) --- .../server/models/job_service/new_job/population_chart.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index a9a2ce57f966c..ab75787a0069f 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -159,6 +159,14 @@ function getPopulationSearchJsonFromConfig( }, }; + if (query.bool === undefined) { + query.bool = { + must: [], + }; + } else if (query.bool.must === undefined) { + query.bool.must = []; + } + query.bool.must.push({ range: { [timeField]: { From 8e8bb4a312b178c7580b20379f85e97f061ec8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 24 Aug 2020 17:12:50 +0200 Subject: [PATCH 02/39] [Security Solution] Fix setting the initial Kibana filters (#75215) (#75752) * [Security Solution] Fix setting the initial Kibana filters * add unit test * keep appState in kibana storage * fix logic Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- src/plugins/data/public/mocks.ts | 2 +- .../components/search_bar/index.test.tsx | 44 +++++++++++ .../common/components/search_bar/index.tsx | 76 ++++++++++++------- 3 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 135b6121d1dd5..0cf97cdf8140e 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -60,7 +60,7 @@ const createStartContract = (): Start => { query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), - SearchBar: jest.fn(), + SearchBar: jest.fn().mockReturnValue(null), }, indexPatterns: ({ createField: jest.fn(() => {}), diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx new file mode 100644 index 0000000000000..356456c777791 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { InputsModelId } from '../../store/inputs/constants'; +import { SearchBarComponent } from '.'; +import { TestProviders } from '../../mock'; + +jest.mock('../../lib/kibana'); + +describe('SearchBarComponent', () => { + const props = { + id: 'global' as InputsModelId, + indexPattern: { + fields: [], + title: '', + }, + updateSearch: jest.fn(), + setSavedQuery: jest.fn(), + setSearchBarFilter: jest.fn(), + end: '', + start: '', + toStr: '', + fromStr: '', + isLoading: false, + filterQuery: { + query: '', + language: '', + }, + queries: [], + savedQuery: undefined, + }; + + it('calls setSearchBarFilter on mount', () => { + mount(, { wrappingComponent: TestProviders }); + + expect(props.setSearchBarFilter).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index de60bca73cedf..2dc44fd48e66d 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -33,7 +33,6 @@ import { filterQuerySelector, fromStrSelector, isLoadingSelector, - kindSelector, queriesSelector, savedQuerySelector, startSelector, @@ -44,6 +43,8 @@ import { networkActions } from '../../../network/store'; import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; +const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState'; + interface SiemSearchBarProps { id: InputsModelId; indexPattern: IIndexPattern; @@ -57,7 +58,7 @@ const SearchBarContainer = styled.div` } `; -const SearchBarComponent = memo( +export const SearchBarComponent = memo( ({ end, filterQuery, @@ -74,20 +75,27 @@ const SearchBarComponent = memo( updateSearch, dataTestSubj, }) => { - const { data } = useKibana().services; const { - timefilter: { timefilter }, - filterManager, - } = data.query; - - if (fromStr != null && toStr != null) { - timefilter.setTime({ from: fromStr, to: toStr }); - } else if (start != null && end != null) { - timefilter.setTime({ - from: new Date(start).toISOString(), - to: new Date(end).toISOString(), - }); - } + data: { + query: { + timefilter: { timefilter }, + filterManager, + }, + ui: { SearchBar }, + }, + storage, + } = useKibana().services; + + useEffect(() => { + if (fromStr != null && toStr != null) { + timefilter.setTime({ from: fromStr, to: toStr }); + } else if (start != null && end != null) { + timefilter.setTime({ + from: new Date(start).toISOString(), + to: new Date(end).toISOString(), + }); + } + }, [end, fromStr, start, timefilter, toStr]); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -135,8 +143,7 @@ const SearchBarComponent = memo( window.setTimeout(() => updateSearch(updateSearchBar), 0); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, filterQuery, fromStr, queries, start, toStr] + [id, toStr, end, fromStr, start, filterManager, filterQuery, queries, updateSearch] ); const onRefresh = useCallback( @@ -155,16 +162,14 @@ const SearchBarComponent = memo( queries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, queries, filterManager] + [updateSearch, id, filterManager, queries] ); const onSaved = useCallback( (newSavedQuery: SavedQuery) => { setSavedQuery({ id, savedQuery: newSavedQuery }); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id] + [id, setSavedQuery] ); const onSavedQueryUpdated = useCallback( @@ -200,8 +205,7 @@ const SearchBarComponent = memo( updateSearch(updateSearchBar); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, fromStr, start, toStr] + [id, toStr, end, fromStr, start, filterManager, updateSearch] ); const onClearSavedQuery = useCallback(() => { @@ -223,8 +227,16 @@ const SearchBarComponent = memo( filterManager, }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, end, filterManager, fromStr, start, toStr, savedQuery]); + }, [savedQuery, updateSearch, id, toStr, end, fromStr, start, filterManager]); + + const saveAppStateToStorage = useCallback( + (filters: Filter[]) => storage.set(APP_STATE_STORAGE_KEY, filters), + [storage] + ); + + const getAppStateFromStorage = useCallback(() => storage.get(APP_STATE_STORAGE_KEY) ?? [], [ + storage, + ]); useEffect(() => { let isSubscribed = true; @@ -234,6 +246,7 @@ const SearchBarComponent = memo( filterManager.getUpdates$().subscribe({ next: () => { if (isSubscribed) { + saveAppStateToStorage(filterManager.getAppFilters()); setSearchBarFilter({ id, filters: filterManager.getFilters(), @@ -243,16 +256,25 @@ const SearchBarComponent = memo( }) ); + // for the initial state + filterManager.setAppFilters(getAppStateFromStorage()); + setSearchBarFilter({ + id, + filters: filterManager.getFilters(), + }); + return () => { isSubscribed = false; subscriptions.unsubscribe(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); + return ( - { const getEndSelector = endSelector(); const getFromStrSelector = fromStrSelector(); const getIsLoadingSelector = isLoadingSelector(); - const getKindSelector = kindSelector(); const getQueriesSelector = queriesSelector(); const getStartSelector = startSelector(); const getToStrSelector = toStrSelector(); @@ -292,7 +313,6 @@ const makeMapStateToProps = () => { fromStr: getFromStrSelector(inputsRange), filterQuery: getFilterQuerySelector(inputsRange), isLoading: getIsLoadingSelector(inputsRange), - kind: getKindSelector(inputsRange), queries: getQueriesSelector(inputsRange), savedQuery: getSavedQuerySelector(inputsRange), start: getStartSelector(inputsRange), From c00241a5b1babde4d173dd402beca58b32b26e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Mon, 24 Aug 2020 19:11:05 +0100 Subject: [PATCH 03/39] [7.9] [Telemetry] Swallow errors in opt-in remote notification from the server (#75641) (#75787) --- src/plugins/telemetry/server/plugin.ts | 1 + src/plugins/telemetry/server/routes/index.ts | 3 ++- .../telemetry/server/routes/telemetry_opt_in.ts | 13 ++++++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 6c8888feafc1f..bd7a2a8c1a8ca 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -89,6 +89,7 @@ export class TelemetryPlugin implements Plugin { config$, currentKibanaVersion, isDev, + logger: this.logger, router, telemetryCollectionManager, }); diff --git a/src/plugins/telemetry/server/routes/index.ts b/src/plugins/telemetry/server/routes/index.ts index ad84cb9d2665d..f46c616a734e0 100644 --- a/src/plugins/telemetry/server/routes/index.ts +++ b/src/plugins/telemetry/server/routes/index.ts @@ -18,7 +18,7 @@ */ import { Observable } from 'rxjs'; -import { IRouter } from 'kibana/server'; +import { IRouter, Logger } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; import { registerTelemetryOptInRoutes } from './telemetry_opt_in'; import { registerTelemetryUsageStatsRoutes } from './telemetry_usage_stats'; @@ -28,6 +28,7 @@ import { TelemetryConfigType } from '../config'; interface RegisterRoutesParams { isDev: boolean; + logger: Logger; config$: Observable; currentKibanaVersion: string; router: IRouter; diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index 7dd15f73029e7..aa1de4b2443a4 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -21,7 +21,7 @@ import moment from 'moment'; import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import { IRouter, Logger } from 'kibana/server'; import { StatsGetterConfig, TelemetryCollectionManagerPluginSetup, @@ -39,12 +39,14 @@ import { TelemetryConfigType } from '../config'; interface RegisterOptInRoutesParams { currentKibanaVersion: string; router: IRouter; + logger: Logger; config$: Observable; telemetryCollectionManager: TelemetryCollectionManagerPluginSetup; } export function registerTelemetryOptInRoutes({ config$, + logger, router, currentKibanaVersion, telemetryCollectionManager, @@ -95,11 +97,16 @@ export function registerTelemetryOptInRoutes({ if (config.sendUsageFrom === 'server') { const optInStatusUrl = config.optInStatusUrl; - await sendTelemetryOptInStatus( + sendTelemetryOptInStatus( telemetryCollectionManager, { optInStatusUrl, newOptInStatus }, statsGetterConfig - ); + ).catch((err) => { + // The server is likely behind a firewall and can't reach the remote service + logger.warn( + `Failed to notify "${optInStatusUrl}" from the server about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}` + ); + }); } await updateTelemetrySavedObject(context.core.savedObjects.client, attributes); From 6e82885aea30227bc87b1208fb2018141f7dd4ac Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Mon, 24 Aug 2020 15:11:30 -0400 Subject: [PATCH 04/39] Filter saved object `version` during legacy import (#75597) (#75793) --- .../server/lib/import/import_dashboards.test.ts | 10 ++++++++-- .../server/lib/import/import_dashboards.ts | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts index 9d4dbb6067946..37e00a8c67fe3 100644 --- a/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts @@ -30,12 +30,18 @@ describe('importDashboards(req)', () => { savedObjectClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); importedObjects = [ - { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' }, references: [] }, + { + id: 'dashboard-01', + type: 'dashboard', + attributes: { panelJSON: '{}' }, + references: [], + version: 'foo', + }, { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' }, references: [] }, ]; }); - test('should call bulkCreate with each asset', async () => { + test('should call bulkCreate with each asset, filtering out any version if present', async () => { await importDashboards(savedObjectClient, importedObjects, { overwrite: false, exclude: [] }); expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); diff --git a/src/plugins/legacy_export/server/lib/import/import_dashboards.ts b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts index 7b7562aecd7bd..8c9eb2fac61af 100644 --- a/src/plugins/legacy_export/server/lib/import/import_dashboards.ts +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts @@ -31,7 +31,8 @@ export async function importDashboards( // docs are not seen as automatically up-to-date. const docs = objects .filter((item) => !exclude.includes(item.type)) - .map((doc) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); + // filter out any document version, if present + .map(({ version, ...doc }) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); const results = await savedObjectsClient.bulkCreate(docs, { overwrite }); return { objects: results.saved_objects }; From 5d6965919beb026b74b39a42e671403e8b3eb8fa Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 24 Aug 2020 14:47:05 -0600 Subject: [PATCH 05/39] Reduces field capabilities event loop block times by scaling linearly using hashes (#75718) (#75805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary On the `security_solution` project from different customers we have been getting reports of scaling issues and excessive NodeJS event blocking times. After in-depth tracing through some of the index and field capabilities calls we identified two of the "hot paths" running through `field_capabilities` to where it is using double looped arrays rather than hashes. By switching these two hot spots out for hashes we are now able to reduce the event loop block times by an order of magnitude. Before this PR you can see event loop block times as high as: ```ts field_cap: 575.131ms ``` And after this PR you will see event loop block times drop by an order of magnitude to: ```ts field_cap: 31.783ms ``` when you're calling into indexes as large as `filebeat-*`. This number can be higher if you're concatenating several large indexes together trying to get capabilities from each one all at once. We already only call `getFieldCapabilities` with one index at a time to spread out event block times. The fix is to use a hash within two key areas within these two files: ```ts src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts ``` This effect happens during the query of `SourceQuery`/`IndexFields` within `security_solution` but you should be able to trigger it with any application code who calls into those code paths with large index sizes such as `filebeat-*` anywhere in Kibana. An explanation of how to see the block times for before and after --- Add, `console.time('field_cap');` and `console.timeEnd('field_cap');` to where the synchronize code is for testing the optimizations of before and after. For example around lines 45 with the original code: ```ts const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(callCluster, indices); console.time('field_cap'); // <--- start timer const fieldsFromFieldCapsByName = keyBy(readFieldCapsResponse(esFieldCaps), 'name'); const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName) .filter((name) => !name.startsWith('_')) .concat(metaFields) .reduce(concatIfUniq, [] as string[]) .map((name) => defaults({}, fieldsFromFieldCapsByName[name], { name, type: 'string', searchable: false, aggregatable: false, readFromDocValues: false, }) ) .map(mergeOverrides); const sorted = sortBy(allFieldsUnsorted, 'name'); console.timeEnd('field_cap'); // <--- outputs the end timer return sorted; ``` And around lines 45 with this pull request: ```ts const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(callCluster, indices); console.time('field_cap'); // <--- start timer const fieldsFromFieldCapsByName = keyBy(readFieldCapsResponse(esFieldCaps), 'name'); const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName) .filter((name) => !name.startsWith('_')) .concat(metaFields) .reduce<{ names: string[]; hash: Record }>( (agg, value) => { // This is intentionally using a "hash" and a "push" to be highly optimized with very large indexes if (agg.hash[value] != null) { return agg; } else { agg.hash[value] = value; agg.names.push(value); return agg; } }, { names: [], hash: {} } ) .names.map((name) => defaults({}, fieldsFromFieldCapsByName[name], { name, type: 'string', searchable: false, aggregatable: false, readFromDocValues: false, }) ) .map(mergeOverrides); const sorted = sortBy(allFieldsUnsorted, 'name'); console.timeEnd('field_cap'); // <--- outputs the end timer return sorted; ``` And then reload the security solutions application web page or generically anything that is going to call filebeat-* index or another large index or you could concatenate several indexes together as well to test out the performance. For security solutions we can just visit any page such as this one below which has a filebeat-* index: ``` http://localhost:5601/app/security/timelines/ ``` Be sure to load it _twice_ for testing as NodeJS will sometimes report better numbers the second time as it does optimizations after the first time it encounters some code paths. You should begin to see numbers similar to this in the before: ```ts field_cap: 575.131ms ``` This indicates that it is blocking the event loop for around half a second before this fix. If an application adds additional indexes on-top of `filebeat`, or if it tries to execute other code after this (which we do in security solutions) then the block times will climb even higher. However, after this fix, the m^n are changed to use hashing so this only climb by some constant * n where n is your fields and for filebeat-* it will should very low around: ```ts field_cap: 31.783ms ``` ### Checklist Unit tests already present, so this shouldn't break anything 🤞 . - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../field_capabilities/field_capabilities.ts | 20 +++++++---- .../field_capabilities/field_caps_response.ts | 34 ++++++++++++++----- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index b4b86b73a5f4a..6b26c82dc95e7 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -25,10 +25,6 @@ import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response' import { mergeOverrides } from './overrides'; import { FieldDescriptor } from '../../index_patterns_fetcher'; -export function concatIfUniq(arr: T[], value: T) { - return arr.includes(value) ? arr : arr.concat(value); -} - /** * Get the field capabilities for field in `indices`, excluding * all internal/underscore-prefixed fields that are not in `metaFields` @@ -49,8 +45,20 @@ export async function getFieldCapabilities( const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName) .filter((name) => !name.startsWith('_')) .concat(metaFields) - .reduce(concatIfUniq, [] as string[]) - .map((name) => + .reduce<{ names: string[]; hash: Record }>( + (agg, value) => { + // This is intentionally using a "hash" and a "push" to be highly optimized with very large indexes + if (agg.hash[value] != null) { + return agg; + } else { + agg.hash[value] = value; + agg.names.push(value); + return agg; + } + }, + { names: [], hash: {} } + ) + .names.map((name) => defaults({}, fieldsFromFieldCapsByName[name], { name, type: 'string', diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index cb1ec6a2ebcf3..861b92569faf2 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -93,8 +93,12 @@ export interface FieldCapsResponse { */ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): FieldDescriptor[] { const capsByNameThenType = fieldCapsResponse.fields; - const kibanaFormattedCaps: FieldDescriptor[] = Object.keys(capsByNameThenType).map( - (fieldName) => { + + const kibanaFormattedCaps = Object.keys(capsByNameThenType).reduce<{ + array: FieldDescriptor[]; + hash: Record; + }>( + (agg, fieldName) => { const capsByType = capsByNameThenType[fieldName]; const types = Object.keys(capsByType); @@ -119,7 +123,7 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie // ignore the conflict and carry on (my wayward son) const uniqueKibanaTypes = uniq(types.map(castEsToKbnFieldTypeName)); if (uniqueKibanaTypes.length > 1) { - return { + const field = { name: fieldName, type: 'conflict', esTypes: types, @@ -134,10 +138,14 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie {} ), }; + // This is intentionally using a "hash" and a "push" to be highly optimized with very large indexes + agg.array.push(field); + agg.hash[fieldName] = field; + return agg; } const esType = types[0]; - return { + const field = { name: fieldName, type: castEsToKbnFieldTypeName(esType), esTypes: types, @@ -145,11 +153,19 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie aggregatable: isAggregatable, readFromDocValues: shouldReadFieldFromDocValues(isAggregatable, esType), }; + // This is intentionally using a "hash" and a "push" to be highly optimized with very large indexes + agg.array.push(field); + agg.hash[fieldName] = field; + return agg; + }, + { + array: [], + hash: {}, } ); // Get all types of sub fields. These could be multi fields or children of nested/object types - const subFields = kibanaFormattedCaps.filter((field) => { + const subFields = kibanaFormattedCaps.array.filter((field) => { return field.name.includes('.'); }); @@ -161,9 +177,9 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie .map((_, index, parentFieldNameParts) => { return parentFieldNameParts.slice(0, index + 1).join('.'); }); - const parentFieldCaps = parentFieldNames.map((parentFieldName) => { - return kibanaFormattedCaps.find((caps) => caps.name === parentFieldName); - }); + const parentFieldCaps = parentFieldNames.map( + (parentFieldName) => kibanaFormattedCaps.hash[parentFieldName] + ); const parentFieldCapsAscending = parentFieldCaps.reverse(); if (parentFieldCaps && parentFieldCaps.length > 0) { @@ -188,7 +204,7 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie } }); - return kibanaFormattedCaps.filter((field) => { + return kibanaFormattedCaps.array.filter((field) => { return !['object', 'nested'].includes(field.type); }); } From 55eb201132875242ebe47072211bed191f67e931 Mon Sep 17 00:00:00 2001 From: Rashmi Kulkarni Date: Mon, 24 Aug 2020 17:26:50 -0700 Subject: [PATCH 06/39] Adding sorting test to scripted fields in discover (#75520) (#75825) ...sorting functional UI tests added. --- .../apps/management/_scripted_fields.js | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 2727313ab2336..9bd4f12de1af4 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -164,6 +164,27 @@ export default function ({ getService, getPageObjects }) { }); }); + //add a test to sort numeric scripted field + it('should sort scripted field value in Discover', async function () { + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName}`); + // after the first click on the scripted field, it becomes secondary sort after time. + // click on the timestamp twice to make it be the secondary sort key. + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('Sep 17, 2015 @ 10:53:14.181\n-1'); + }); + + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.479\n20'); + }); + }); + it('should filter by scripted field value in Discover', async function () { await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); await log.debug('filter by the first value (14) in the expanded scripted field list'); @@ -251,6 +272,27 @@ export default function ({ getService, getPageObjects }) { }); }); + //add a test to sort string scripted field + it('should sort scripted field value in Discover', async function () { + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + // after the first click on the scripted field, it becomes secondary sort after time. + // click on the timestamp twice to make it be the secondary sort key. + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('Sep 17, 2015 @ 09:48:40.594\nbad'); + }); + + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.479\ngood'); + }); + }); + it('should filter by scripted field value in Discover', async function () { await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await log.debug('filter by "bad" in the expanded scripted field list'); @@ -329,6 +371,28 @@ export default function ({ getService, getPageObjects }) { await filterBar.removeAllFilters(); }); + //add a test to sort boolean + //existing bug: https://github.com/elastic/kibana/issues/75519 hence the issue is skipped. + it.skip('should sort scripted field value in Discover', async function () { + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + // after the first click on the scripted field, it becomes secondary sort after time. + // click on the timestamp twice to make it be the secondary sort key. + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('updateExpectedResultHere\ntrue'); + }); + + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('updateExpectedResultHere\nfalse'); + }); + }); + it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -383,6 +447,28 @@ export default function ({ getService, getPageObjects }) { }); }); + //add a test to sort date scripted field + //https://github.com/elastic/kibana/issues/75711 + it.skip('should sort scripted field value in Discover', async function () { + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + // after the first click on the scripted field, it becomes secondary sort after time. + // click on the timestamp twice to make it be the secondary sort key. + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('updateExpectedResultHere\n2015-09-18 07:00'); + }); + + await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = await PageObjects.discover.getDocTableIndex(1); + expect(rowData).to.be('updateExpectedResultHere\n2015-09-18 07:00'); + }); + }); + it('should filter by scripted field value in Discover', async function () { await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await log.debug('filter by "Sep 17, 2015 @ 23:00" in the expanded scripted field list'); From 01a260c1caece18ecae8289e53b363bef5c9b7ce Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Mon, 24 Aug 2020 17:43:51 -0700 Subject: [PATCH 07/39] [Canvas][Docs] Adds `var` and `var_set` to expression function reference (#74291) (#75833) --- .../canvas/canvas-function-reference.asciidoc | 55 ++++++++++++++++++- .../common/expression_functions/specs/var.ts | 4 +- .../expression_functions/specs/var_set.ts | 6 +- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 657e3ec8b8bb1..73654784efce0 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -13,7 +13,7 @@ A *** denotes a required argument. A † denotes an argument can be passed multiple times. -<> | B | <> | <> | <> | <> | <> | <> | <> | <> | K | <> | <> | <> | O | <> | Q | <> | <> | <> | <> | V | W | X | Y | Z +<> | B | <> | <> | <> | <> | <> | <> | <> | <> | K | <> | <> | <> | O | <> | Q | <> | <> | <> | <> | <> | W | X | Y | Z [float] [[a_fns]] @@ -2871,3 +2871,56 @@ Default: `""` |=== *Returns:* `string` + +[float] +[[v_fns]] +== V + +[float] +[[var_fn]] +=== `var` + +Updates the Kibana global context. + +*Accepts:* `any` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|_Unnamed_ *** + +Alias: `name` +|`string` +|Specify the name of the variable. +|=== + +*Returns:* Depends on your input and arguments + + +[float] +[[var_set_fn]] +=== `var_set` + +Updates the Kibana global context. + +*Accepts:* `any` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|_Unnamed_ *** + +Alias: `name` +|`string` +|Specify the name of the variable. + +|`value` + +Alias: `val` +|`any` +|Specify the value for the variable. When unspecified, the input context is used. +|=== + +*Returns:* Depends on your input and arguments diff --git a/src/plugins/expressions/common/expression_functions/specs/var.ts b/src/plugins/expressions/common/expression_functions/specs/var.ts index 4bc185a4cadfd..7d95c9816b99c 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var.ts @@ -34,7 +34,7 @@ export type ExpressionFunctionVar = ExpressionFunctionDefinition< export const variable: ExpressionFunctionVar = { name: 'var', help: i18n.translate('expressions.functions.var.help', { - defaultMessage: 'Updates kibana global context', + defaultMessage: 'Updates the Kibana global context.', }), args: { name: { @@ -42,7 +42,7 @@ export const variable: ExpressionFunctionVar = { aliases: ['_'], required: true, help: i18n.translate('expressions.functions.var.name.help', { - defaultMessage: 'Specify name of the variable', + defaultMessage: 'Specify the name of the variable.', }), }, }, diff --git a/src/plugins/expressions/common/expression_functions/specs/var_set.ts b/src/plugins/expressions/common/expression_functions/specs/var_set.ts index 8f15bc8b90042..c45ca593f020c 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var_set.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var_set.ts @@ -35,7 +35,7 @@ export type ExpressionFunctionVarSet = ExpressionFunctionDefinition< export const variableSet: ExpressionFunctionVarSet = { name: 'var_set', help: i18n.translate('expressions.functions.varset.help', { - defaultMessage: 'Updates kibana global context', + defaultMessage: 'Updates the Kibana global context.', }), args: { name: { @@ -43,14 +43,14 @@ export const variableSet: ExpressionFunctionVarSet = { aliases: ['_'], required: true, help: i18n.translate('expressions.functions.varset.name.help', { - defaultMessage: 'Specify name of the variable', + defaultMessage: 'Specify the name of the variable.', }), }, value: { aliases: ['val'], help: i18n.translate('expressions.functions.varset.val.help', { defaultMessage: - 'Specify value for the variable. If not provided input context will be used', + 'Specify the value for the variable. When unspecified, the input context is used.', }), }, }, From 111a3ee6bcde435e54945339179793ec929dd335 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 25 Aug 2020 12:48:32 +0100 Subject: [PATCH 08/39] chore(NA): setup backport tool for 7.9 and the new 7.x (#71861) (#75835) Co-authored-by: Tyler Smalley # Conflicts: # .backportrc.json # src/dev/renovate/config.ts --- .backportrc.json | 32 +- renovate.json5 | 1117 ---------------------------------------------- 2 files changed, 30 insertions(+), 1119 deletions(-) delete mode 100644 renovate.json5 diff --git a/.backportrc.json b/.backportrc.json index 30b18b7561eaa..3f1d639e9a480 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,5 +1,33 @@ { "upstream": "elastic/kibana", - "branches": [{ "name": "7.x", "checked": true }, "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], - "labels": ["backport"] + "targetBranchChoices": [ + { "name": "master", "checked": true }, + { "name": "7.x", "checked": true }, + "7.9", + "7.8", + "7.7", + "7.6", + "7.5", + "7.4", + "7.3", + "7.2", + "7.1", + "7.0", + "6.8", + "6.7", + "6.6", + "6.5", + "6.4", + "6.3", + "6.2", + "6.1", + "6.0", + "5.6" + ], + "targetPRLabels": ["backport"], + "branchLabelMapping": { + "^v8.0.0$": "master", + "^v7.10.0$": "7.x", + "^v(\\d+).(\\d+).\\d+$": "$1.$2" + } } diff --git a/renovate.json5 b/renovate.json5 deleted file mode 100644 index 9213d405d1697..0000000000000 --- a/renovate.json5 +++ /dev/null @@ -1,1117 +0,0 @@ -/** - * PLEASE DO NOT MODIFY - * - * This file is automatically generated by running `node scripts/build_renovate_config` - * - */ -{ - extends: [ - 'config:base', - ], - includePaths: [ - 'package.json', - 'x-pack/package.json', - 'x-pack/legacy/plugins/*/package.json', - 'packages/*/package.json', - 'examples/*/package.json', - 'test/plugin_functional/plugins/*/package.json', - 'test/interpreter_functional/plugins/*/package.json', - ], - baseBranches: [ - 'master', - ], - labels: [ - 'release_note:skip', - 'Team:Operations', - 'renovate', - 'v8.0.0', - 'v7.9.0', - ], - major: { - labels: [ - 'release_note:skip', - 'Team:Operations', - 'renovate', - 'v8.0.0', - 'v7.9.0', - 'renovate:major', - ], - }, - separateMajorMinor: false, - masterIssue: true, - masterIssueApproval: true, - rangeStrategy: 'bump', - npm: { - lockFileMaintenance: { - enabled: false, - }, - packageRules: [ - { - groupSlug: '@elastic/charts', - groupName: '@elastic/charts related packages', - packageNames: [ - '@elastic/charts', - '@types/elastic__charts', - ], - reviewers: [ - 'markov00', - ], - masterIssueApproval: false, - }, - { - groupSlug: '@reach/router', - groupName: '@reach/router related packages', - packageNames: [ - '@reach/router', - '@types/reach__router', - ], - }, - { - groupSlug: '@testing-library/dom', - groupName: '@testing-library/dom related packages', - packageNames: [ - '@testing-library/dom', - '@types/testing-library__dom', - ], - }, - { - groupSlug: 'angular', - groupName: 'angular related packages', - packagePatterns: [ - '(\\b|_)angular(\\b|_)', - ], - }, - { - groupSlug: 'api-documenter', - groupName: 'api-documenter related packages', - packageNames: [ - '@microsoft/api-documenter', - '@types/microsoft__api-documenter', - '@microsoft/api-extractor', - '@types/microsoft__api-extractor', - ], - enabled: false, - }, - { - groupSlug: 'archiver', - groupName: 'archiver related packages', - packageNames: [ - 'archiver', - '@types/archiver', - ], - }, - { - groupSlug: 'babel', - groupName: 'babel related packages', - packagePatterns: [ - '(\\b|_)babel(\\b|_)', - ], - packageNames: [ - 'core-js', - '@types/core-js', - '@babel/preset-react', - '@types/babel__preset-react', - '@babel/preset-typescript', - '@types/babel__preset-typescript', - ], - }, - { - groupSlug: 'base64-js', - groupName: 'base64-js related packages', - packageNames: [ - 'base64-js', - '@types/base64-js', - ], - }, - { - groupSlug: 'bluebird', - groupName: 'bluebird related packages', - packageNames: [ - 'bluebird', - '@types/bluebird', - ], - }, - { - groupSlug: 'browserslist-useragent', - groupName: 'browserslist-useragent related packages', - packageNames: [ - 'browserslist-useragent', - '@types/browserslist-useragent', - ], - }, - { - groupSlug: 'chance', - groupName: 'chance related packages', - packageNames: [ - 'chance', - '@types/chance', - ], - }, - { - groupSlug: 'cheerio', - groupName: 'cheerio related packages', - packageNames: [ - 'cheerio', - '@types/cheerio', - ], - }, - { - groupSlug: 'chroma-js', - groupName: 'chroma-js related packages', - packageNames: [ - 'chroma-js', - '@types/chroma-js', - ], - }, - { - groupSlug: 'chromedriver', - groupName: 'chromedriver related packages', - packageNames: [ - 'chromedriver', - '@types/chromedriver', - ], - }, - { - groupSlug: 'classnames', - groupName: 'classnames related packages', - packageNames: [ - 'classnames', - '@types/classnames', - ], - }, - { - groupSlug: 'cmd-shim', - groupName: 'cmd-shim related packages', - packageNames: [ - 'cmd-shim', - '@types/cmd-shim', - ], - }, - { - groupSlug: 'color', - groupName: 'color related packages', - packageNames: [ - 'color', - '@types/color', - ], - }, - { - groupSlug: 'cpy', - groupName: 'cpy related packages', - packageNames: [ - 'cpy', - '@types/cpy', - ], - }, - { - groupSlug: 'cytoscape', - groupName: 'cytoscape related packages', - packageNames: [ - 'cytoscape', - '@types/cytoscape', - ], - }, - { - groupSlug: 'd3', - groupName: 'd3 related packages', - packagePatterns: [ - '(\\b|_)d3(\\b|_)', - ], - }, - { - groupSlug: 'dedent', - groupName: 'dedent related packages', - packageNames: [ - 'dedent', - '@types/dedent', - ], - }, - { - groupSlug: 'deep-freeze-strict', - groupName: 'deep-freeze-strict related packages', - packageNames: [ - 'deep-freeze-strict', - '@types/deep-freeze-strict', - ], - }, - { - groupSlug: 'delete-empty', - groupName: 'delete-empty related packages', - packageNames: [ - 'delete-empty', - '@types/delete-empty', - ], - }, - { - groupSlug: 'dragselect', - groupName: 'dragselect related packages', - packageNames: [ - 'dragselect', - '@types/dragselect', - ], - labels: [ - 'release_note:skip', - 'Team:Operations', - 'renovate', - 'v8.0.0', - 'v7.9.0', - ':ml', - ], - }, - { - groupSlug: 'elasticsearch', - groupName: 'elasticsearch related packages', - packageNames: [ - 'elasticsearch', - '@types/elasticsearch', - ], - }, - { - groupSlug: 'eslint', - groupName: 'eslint related packages', - packagePatterns: [ - '(\\b|_)eslint(\\b|_)', - ], - }, - { - groupSlug: 'estree', - groupName: 'estree related packages', - packageNames: [ - 'estree', - '@types/estree', - ], - }, - { - groupSlug: 'fancy-log', - groupName: 'fancy-log related packages', - packageNames: [ - 'fancy-log', - '@types/fancy-log', - ], - }, - { - groupSlug: 'fetch-mock', - groupName: 'fetch-mock related packages', - packageNames: [ - 'fetch-mock', - '@types/fetch-mock', - ], - }, - { - groupSlug: 'file-saver', - groupName: 'file-saver related packages', - packageNames: [ - 'file-saver', - '@types/file-saver', - ], - }, - { - groupSlug: 'flot', - groupName: 'flot related packages', - packageNames: [ - 'flot', - '@types/flot', - ], - }, - { - groupSlug: 'geojson', - groupName: 'geojson related packages', - packageNames: [ - 'geojson', - '@types/geojson', - ], - }, - { - groupSlug: 'getopts', - groupName: 'getopts related packages', - packageNames: [ - 'getopts', - '@types/getopts', - ], - }, - { - groupSlug: 'getos', - groupName: 'getos related packages', - packageNames: [ - 'getos', - '@types/getos', - ], - }, - { - groupSlug: 'git-url-parse', - groupName: 'git-url-parse related packages', - packageNames: [ - 'git-url-parse', - '@types/git-url-parse', - ], - }, - { - groupSlug: 'glob', - groupName: 'glob related packages', - packageNames: [ - 'glob', - '@types/glob', - ], - }, - { - groupSlug: 'globby', - groupName: 'globby related packages', - packageNames: [ - 'globby', - '@types/globby', - ], - }, - { - groupSlug: 'graphql', - groupName: 'graphql related packages', - packagePatterns: [ - '(\\b|_)graphql(\\b|_)', - '(\\b|_)apollo(\\b|_)', - ], - }, - { - groupSlug: 'grunt', - groupName: 'grunt related packages', - packagePatterns: [ - '(\\b|_)grunt(\\b|_)', - ], - }, - { - groupSlug: 'gulp', - groupName: 'gulp related packages', - packagePatterns: [ - '(\\b|_)gulp(\\b|_)', - ], - }, - { - groupSlug: 'hapi', - groupName: 'hapi related packages', - packagePatterns: [ - '(\\b|_)hapi(\\b|_)', - ], - packageNames: [ - 'hapi', - '@types/hapi', - 'joi', - '@types/joi', - 'boom', - '@types/boom', - 'hoek', - '@types/hoek', - 'h2o2', - '@types/h2o2', - '@elastic/good', - '@types/elastic__good', - 'good-squeeze', - '@types/good-squeeze', - 'inert', - '@types/inert', - 'accept', - '@types/accept', - ], - }, - { - groupSlug: 'has-ansi', - groupName: 'has-ansi related packages', - packageNames: [ - 'has-ansi', - '@types/has-ansi', - ], - }, - { - groupSlug: 'he', - groupName: 'he related packages', - packageNames: [ - 'he', - '@types/he', - ], - }, - { - groupSlug: 'history', - groupName: 'history related packages', - packageNames: [ - 'history', - '@types/history', - ], - }, - { - groupSlug: 'inquirer', - groupName: 'inquirer related packages', - packageNames: [ - 'inquirer', - '@types/inquirer', - ], - }, - { - groupSlug: 'intl-relativeformat', - groupName: 'intl-relativeformat related packages', - packageNames: [ - 'intl-relativeformat', - '@types/intl-relativeformat', - ], - }, - { - groupSlug: 'jest', - groupName: 'jest related packages', - packagePatterns: [ - '(\\b|_)jest(\\b|_)', - ], - }, - { - groupSlug: 'jquery', - groupName: 'jquery related packages', - packageNames: [ - 'jquery', - '@types/jquery', - ], - }, - { - groupSlug: 'js-search', - groupName: 'js-search related packages', - packageNames: [ - 'js-search', - '@types/js-search', - ], - }, - { - groupSlug: 'js-yaml', - groupName: 'js-yaml related packages', - packageNames: [ - 'js-yaml', - '@types/js-yaml', - ], - }, - { - groupSlug: 'jsdom', - groupName: 'jsdom related packages', - packageNames: [ - 'jsdom', - '@types/jsdom', - ], - }, - { - groupSlug: 'json-stable-stringify', - groupName: 'json-stable-stringify related packages', - packageNames: [ - 'json-stable-stringify', - '@types/json-stable-stringify', - ], - }, - { - groupSlug: 'json5', - groupName: 'json5 related packages', - packageNames: [ - 'json5', - '@types/json5', - ], - }, - { - groupSlug: 'jsonwebtoken', - groupName: 'jsonwebtoken related packages', - packageNames: [ - 'jsonwebtoken', - '@types/jsonwebtoken', - ], - }, - { - groupSlug: 'jsts', - groupName: 'jsts related packages', - packageNames: [ - 'jsts', - '@types/jsts', - ], - allowedVersions: '^1.6.2', - }, - { - groupSlug: 'karma', - groupName: 'karma related packages', - packagePatterns: [ - '(\\b|_)karma(\\b|_)', - ], - }, - { - groupSlug: 'language server', - groupName: 'language server related packages', - packageNames: [ - 'vscode-jsonrpc', - '@types/vscode-jsonrpc', - 'vscode-languageserver', - '@types/vscode-languageserver', - 'vscode-languageserver-types', - '@types/vscode-languageserver-types', - ], - }, - { - groupSlug: 'license-checker', - groupName: 'license-checker related packages', - packageNames: [ - 'license-checker', - '@types/license-checker', - ], - }, - { - groupSlug: 'listr', - groupName: 'listr related packages', - packageNames: [ - 'listr', - '@types/listr', - ], - }, - { - groupSlug: 'lodash', - groupName: 'lodash related packages', - packageNames: [ - 'lodash', - '@types/lodash', - ], - }, - { - groupSlug: 'log-symbols', - groupName: 'log-symbols related packages', - packageNames: [ - 'log-symbols', - '@types/log-symbols', - ], - }, - { - groupSlug: 'lru-cache', - groupName: 'lru-cache related packages', - packageNames: [ - 'lru-cache', - '@types/lru-cache', - ], - }, - { - groupSlug: 'mapbox-gl', - groupName: 'mapbox-gl related packages', - packageNames: [ - 'mapbox-gl', - '@types/mapbox-gl', - ], - }, - { - groupSlug: 'markdown-it', - groupName: 'markdown-it related packages', - packageNames: [ - 'markdown-it', - '@types/markdown-it', - ], - }, - { - groupSlug: 'memoize-one', - groupName: 'memoize-one related packages', - packageNames: [ - 'memoize-one', - '@types/memoize-one', - ], - }, - { - groupSlug: 'mime', - groupName: 'mime related packages', - packageNames: [ - 'mime', - '@types/mime', - ], - }, - { - groupSlug: 'minimatch', - groupName: 'minimatch related packages', - packageNames: [ - 'minimatch', - '@types/minimatch', - ], - }, - { - groupSlug: 'mocha', - groupName: 'mocha related packages', - packagePatterns: [ - '(\\b|_)mocha(\\b|_)', - ], - }, - { - groupSlug: 'mock-fs', - groupName: 'mock-fs related packages', - packageNames: [ - 'mock-fs', - '@types/mock-fs', - ], - }, - { - groupSlug: 'moment', - groupName: 'moment related packages', - packagePatterns: [ - '(\\b|_)moment(\\b|_)', - ], - }, - { - groupSlug: 'mustache', - groupName: 'mustache related packages', - packageNames: [ - 'mustache', - '@types/mustache', - ], - }, - { - groupSlug: 'ncp', - groupName: 'ncp related packages', - packageNames: [ - 'ncp', - '@types/ncp', - ], - }, - { - groupSlug: 'nock', - groupName: 'nock related packages', - packageNames: [ - 'nock', - '@types/nock', - ], - }, - { - groupSlug: 'node', - groupName: 'node related packages', - packageNames: [ - 'node', - '@types/node', - ], - }, - { - groupSlug: 'node-fetch', - groupName: 'node-fetch related packages', - packageNames: [ - 'node-fetch', - '@types/node-fetch', - ], - }, - { - groupSlug: 'node-forge', - groupName: 'node-forge related packages', - packageNames: [ - 'node-forge', - '@types/node-forge', - ], - }, - { - groupSlug: 'node-sass', - groupName: 'node-sass related packages', - packageNames: [ - 'node-sass', - '@types/node-sass', - ], - }, - { - groupSlug: 'nodemailer', - groupName: 'nodemailer related packages', - packageNames: [ - 'nodemailer', - '@types/nodemailer', - ], - }, - { - groupSlug: 'normalize-path', - groupName: 'normalize-path related packages', - packageNames: [ - 'normalize-path', - '@types/normalize-path', - ], - }, - { - groupSlug: 'object-hash', - groupName: 'object-hash related packages', - packageNames: [ - 'object-hash', - '@types/object-hash', - ], - }, - { - groupSlug: 'opn', - groupName: 'opn related packages', - packageNames: [ - 'opn', - '@types/opn', - ], - }, - { - groupSlug: 'ora', - groupName: 'ora related packages', - packageNames: [ - 'ora', - '@types/ora', - ], - }, - { - groupSlug: 'papaparse', - groupName: 'papaparse related packages', - packageNames: [ - 'papaparse', - '@types/papaparse', - ], - }, - { - groupSlug: 'parse-link-header', - groupName: 'parse-link-header related packages', - packageNames: [ - 'parse-link-header', - '@types/parse-link-header', - ], - }, - { - groupSlug: 'pegjs', - groupName: 'pegjs related packages', - packageNames: [ - 'pegjs', - '@types/pegjs', - ], - }, - { - groupSlug: 'pngjs', - groupName: 'pngjs related packages', - packageNames: [ - 'pngjs', - '@types/pngjs', - ], - }, - { - groupSlug: 'podium', - groupName: 'podium related packages', - packageNames: [ - 'podium', - '@types/podium', - ], - }, - { - groupSlug: 'pretty-ms', - groupName: 'pretty-ms related packages', - packageNames: [ - 'pretty-ms', - '@types/pretty-ms', - ], - }, - { - groupSlug: 'proper-lockfile', - groupName: 'proper-lockfile related packages', - packageNames: [ - 'proper-lockfile', - '@types/proper-lockfile', - ], - }, - { - groupSlug: 'puppeteer', - groupName: 'puppeteer related packages', - packageNames: [ - 'puppeteer', - '@types/puppeteer', - ], - }, - { - groupSlug: 'react', - groupName: 'react related packages', - packagePatterns: [ - '(\\b|_)react(\\b|_)', - '(\\b|_)redux(\\b|_)', - '(\\b|_)enzyme(\\b|_)', - ], - packageNames: [ - 'ngreact', - '@types/ngreact', - 'recompose', - '@types/recompose', - 'prop-types', - '@types/prop-types', - 'typescript-fsa-reducers', - '@types/typescript-fsa-reducers', - 'reselect', - '@types/reselect', - ], - }, - { - groupSlug: 'read-pkg', - groupName: 'read-pkg related packages', - packageNames: [ - 'read-pkg', - '@types/read-pkg', - ], - }, - { - groupSlug: 'reduce-reducers', - groupName: 'reduce-reducers related packages', - packageNames: [ - 'reduce-reducers', - '@types/reduce-reducers', - ], - }, - { - groupSlug: 'request', - groupName: 'request related packages', - packageNames: [ - 'request', - '@types/request', - ], - }, - { - groupSlug: 'selenium-webdriver', - groupName: 'selenium-webdriver related packages', - packageNames: [ - 'selenium-webdriver', - '@types/selenium-webdriver', - ], - }, - { - groupSlug: 'semver', - groupName: 'semver related packages', - packageNames: [ - 'semver', - '@types/semver', - ], - }, - { - groupSlug: 'set-value', - groupName: 'set-value related packages', - packageNames: [ - 'set-value', - '@types/set-value', - ], - }, - { - groupSlug: 'sinon', - groupName: 'sinon related packages', - packageNames: [ - 'sinon', - '@types/sinon', - ], - }, - { - groupSlug: 'stats-lite', - groupName: 'stats-lite related packages', - packageNames: [ - 'stats-lite', - '@types/stats-lite', - ], - }, - { - groupSlug: 'storybook', - groupName: 'storybook related packages', - packagePatterns: [ - '(\\b|_)storybook(\\b|_)', - ], - }, - { - groupSlug: 'strip-ansi', - groupName: 'strip-ansi related packages', - packageNames: [ - 'strip-ansi', - '@types/strip-ansi', - ], - }, - { - groupSlug: 'strong-log-transformer', - groupName: 'strong-log-transformer related packages', - packageNames: [ - 'strong-log-transformer', - '@types/strong-log-transformer', - ], - }, - { - groupSlug: 'styled-components', - groupName: 'styled-components related packages', - packageNames: [ - 'styled-components', - '@types/styled-components', - ], - }, - { - groupSlug: 'supertest', - groupName: 'supertest related packages', - packageNames: [ - 'supertest', - '@types/supertest', - ], - }, - { - groupSlug: 'supertest-as-promised', - groupName: 'supertest-as-promised related packages', - packageNames: [ - 'supertest-as-promised', - '@types/supertest-as-promised', - ], - }, - { - groupSlug: 'tar', - groupName: 'tar related packages', - packageNames: [ - 'tar', - '@types/tar', - ], - }, - { - groupSlug: 'tar-fs', - groupName: 'tar-fs related packages', - packageNames: [ - 'tar-fs', - '@types/tar-fs', - ], - }, - { - groupSlug: 'tempy', - groupName: 'tempy related packages', - packageNames: [ - 'tempy', - '@types/tempy', - ], - }, - { - groupSlug: 'through2', - groupName: 'through2 related packages', - packageNames: [ - 'through2', - '@types/through2', - ], - }, - { - groupSlug: 'through2-map', - groupName: 'through2-map related packages', - packageNames: [ - 'through2-map', - '@types/through2-map', - ], - }, - { - groupSlug: 'tinycolor2', - groupName: 'tinycolor2 related packages', - packageNames: [ - 'tinycolor2', - '@types/tinycolor2', - ], - }, - { - groupSlug: 'type-detect', - groupName: 'type-detect related packages', - packageNames: [ - 'type-detect', - '@types/type-detect', - ], - }, - { - groupSlug: 'typescript', - groupName: 'typescript related packages', - packagePatterns: [ - '(\\b|_)ts(\\b|_)', - '(\\b|_)typescript(\\b|_)', - ], - packageNames: [ - 'tslib', - '@types/tslib', - ], - }, - { - groupSlug: 'use-resize-observer', - groupName: 'use-resize-observer related packages', - packageNames: [ - 'use-resize-observer', - '@types/use-resize-observer', - ], - }, - { - groupSlug: 'uuid', - groupName: 'uuid related packages', - packageNames: [ - 'uuid', - '@types/uuid', - ], - }, - { - groupSlug: 'vega', - groupName: 'vega related packages', - packagePatterns: [ - '(\\b|_)vega(\\b|_)', - ], - enabled: false, - }, - { - groupSlug: 'vinyl', - groupName: 'vinyl related packages', - packageNames: [ - 'vinyl', - '@types/vinyl', - ], - }, - { - groupSlug: 'vinyl-fs', - groupName: 'vinyl-fs related packages', - packageNames: [ - 'vinyl-fs', - '@types/vinyl-fs', - ], - }, - { - groupSlug: 'watchpack', - groupName: 'watchpack related packages', - packageNames: [ - 'watchpack', - '@types/watchpack', - ], - }, - { - groupSlug: 'webpack', - groupName: 'webpack related packages', - packagePatterns: [ - '(\\b|_)webpack(\\b|_)', - '(\\b|_)loader(\\b|_)', - '(\\b|_)acorn(\\b|_)', - '(\\b|_)terser(\\b|_)', - ], - packageNames: [ - 'mini-css-extract-plugin', - '@types/mini-css-extract-plugin', - 'chokidar', - '@types/chokidar', - ], - }, - { - groupSlug: 'write-pkg', - groupName: 'write-pkg related packages', - packageNames: [ - 'write-pkg', - '@types/write-pkg', - ], - }, - { - groupSlug: 'xml-crypto', - groupName: 'xml-crypto related packages', - packageNames: [ - 'xml-crypto', - '@types/xml-crypto', - ], - }, - { - groupSlug: 'xml2js', - groupName: 'xml2js related packages', - packageNames: [ - 'xml2js', - '@types/xml2js', - ], - }, - { - groupSlug: 'zen-observable', - groupName: 'zen-observable related packages', - packageNames: [ - 'zen-observable', - '@types/zen-observable', - ], - }, - { - packagePatterns: [ - '^@kbn/.*', - ], - enabled: false, - }, - ], - }, - prConcurrentLimit: 0, - vulnerabilityAlerts: { - enabled: false, - }, - rebaseStalePrs: false, - rebaseConflictedPrs: false, - semanticCommits: false, -} From 7a240dc9bd0ed2b264dcf636cf64581e21bdff21 Mon Sep 17 00:00:00 2001 From: Marius Dragomir Date: Tue, 25 Aug 2020 14:47:36 +0200 Subject: [PATCH 09/39] Handle change in saml VM name (#73808) (#75760) * Handle change in saml VM name * fix lint problem * Update login_page.ts * Fix tslint Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- test/functional/page_objects/login_page.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/functional/page_objects/login_page.ts b/test/functional/page_objects/login_page.ts index 350ab8be1a274..6cab2d39f3a9e 100644 --- a/test/functional/page_objects/login_page.ts +++ b/test/functional/page_objects/login_page.ts @@ -48,10 +48,8 @@ export function LoginPageProvider({ getService }: FtrProviderContext) { class LoginPage { async login(user: string, pwd: string) { - if ( - process.env.VM === 'ubuntu18_deb_oidc' || - process.env.VM === 'ubuntu16_deb_desktop_saml' - ) { + const loginType = process.env.VM || ''; + if (loginType.includes('oidc') || loginType.includes('saml')) { await samlLogin(user, pwd); return; } From 2c7f0e8ac28bd1ce2d098a76a63005e78691ef99 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 25 Aug 2020 15:33:30 +0200 Subject: [PATCH 10/39] [Visualize] fix performance degradation after lodash@4 upgrade (#75865) --- src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js b/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js index 6490dfe252b29..dda9d85ec43c5 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js @@ -41,7 +41,7 @@ export class VisConfig { const visType = visTypes[visConfigArgs.type]; const typeDefaults = visType(visConfigArgs, this.data); - this._values = _.defaultsDeep({}, typeDefaults, DEFAULT_VIS_CONFIG); + this._values = _.defaultsDeep({ ...typeDefaults }, DEFAULT_VIS_CONFIG); this._values.el = el; } From 824594052f39db9f8e48b3c7eb60816ed5fd8593 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:27:19 -0700 Subject: [PATCH 11/39] [7.9] [DOCS] Adds redirect for rbac content (#75803) (#75814) * [DOCS] Adds redirect for rbac content (#75803) * [DOCS] Fixes build error * [DOCS] Keeps content on same page * [DOCS] Fixes level offset * [DOCS] Resets section level changes Co-authored-by: lcawl --- docs/developer/architecture/security/index.asciidoc | 2 +- docs/developer/architecture/security/rbac.asciidoc | 10 +++++++--- docs/redirects.asciidoc | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/developer/architecture/security/index.asciidoc b/docs/developer/architecture/security/index.asciidoc index 55b2450caf7a7..802c6634ec741 100644 --- a/docs/developer/architecture/security/index.asciidoc +++ b/docs/developer/architecture/security/index.asciidoc @@ -1,7 +1,7 @@ [[development-security]] === Security -{kib} has generally been able to implement security transparently to core and plugin developers, and this largely remains the case. {kib} on two methods that the elasticsearch `Cluster` provides: `callWithRequest` and `callWithInternalUser`. +{kib} has generally been able to implement security transparently to core and plugin developers, and this largely remains the case. {kib} on two methods that the elasticsearch `Cluster` provides: `callWithRequest` and `callWithInternalUser`. `callWithRequest` executes requests against Elasticsearch using the authentication credentials of the {kib} end-user. So, if you log into {kib} with the user of `foo` when `callWithRequest` is used, {kib} execute the request against Elasticsearch as the user `foo`. Historically, `callWithRequest` has been used extensively to perform actions that are initiated at the request of {kib} end-users. diff --git a/docs/developer/architecture/security/rbac.asciidoc b/docs/developer/architecture/security/rbac.asciidoc index ae1979e856e23..7fd73cf358884 100644 --- a/docs/developer/architecture/security/rbac.asciidoc +++ b/docs/developer/architecture/security/rbac.asciidoc @@ -1,4 +1,5 @@ -[[development-security-rbac]] +[discrete] +[[development-rbac]] ==== Role-based access control Role-based access control (RBAC) in {kib} relies upon the @@ -7,9 +8,10 @@ that Elasticsearch exposes. This allows {kib} to define the privileges that {kib} wishes to grant to users, assign them to the relevant users using roles, and then authorize the user to perform a specific action. This is handled within a secured instance of the `SavedObjectsClient` and available transparently to -consumers when using `request.getSavedObjectsClient()` or +consumers when using `request.getSavedObjectsClient()` or `savedObjects.getScopedSavedObjectsClient()`. +[discrete] [[development-rbac-privileges]] ===== {kib} Privileges @@ -55,6 +57,7 @@ The application is created by concatenating the prefix of `kibana-` with the val ============================================== +[discrete] [[development-rbac-assigning-privileges]] ===== Assigning {kib} Privileges @@ -77,9 +80,10 @@ The application is created by concatenating the prefix of `kibana-` with the val } ---------------------------------- -Roles that grant <> should be managed using the <> or the *Management -> Security -> Roles* page, not directly using the {es} {ref}/security-api.html#security-role-apis[role management API]. This role can then be assigned to users using the {es} +Roles that grant <> should be managed using the <> or the *Management -> Security -> Roles* page, not directly using the {es} {ref}/security-api.html#security-role-apis[role management API]. This role can then be assigned to users using the {es} {ref}/security-api.html#security-user-apis[user management APIs]. +[discrete] [[development-rbac-authorization]] ===== Authorization diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index cde588ee731d2..e48267fa82a5b 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -106,3 +106,8 @@ Watcher error reports have been removed and replaced with Kibana's <>. + +[role="exclude",id="development-security-rbac"] +== Role-based access control + +This content has moved to the <> page. From c7715934205df35ac3e6e0e1b5229de8367aa6cf Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 25 Aug 2020 21:12:27 -0400 Subject: [PATCH 12/39] [Security Solution][Detections] Disables add exception for ML and threshold rules (#75802) (#75937) --- .../components/alerts_table/default_config.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index f38a9107afca9..d855854e6817c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -11,6 +11,8 @@ import ApolloClient from 'apollo-client'; import { Dispatch } from 'redux'; import { EuiText } from '@elastic/eui'; +import { RuleType } from '../../../../common/detection_engine/types'; +import { isMlRule } from '../../../../common/machine_learning/helpers'; import { RowRendererId } from '../../../../common/types/timeline'; import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; @@ -41,6 +43,7 @@ import { import { Ecs, TimelineNonEcsData } from '../../../graphql/types'; import { AddExceptionModalBaseProps } from '../../../common/components/exceptions/add_exception_modal'; import { getMappedNonEcsValue } from '../../../common/components/exceptions/helpers'; +import { isThresholdRule } from '../../../../common/detection_engine/utils'; export const buildAlertStatusFilter = (status: Status): Filter[] => [ { @@ -195,6 +198,7 @@ export const requiredFieldsForActions = [ 'signal.rule.query', 'signal.rule.to', 'signal.rule.id', + 'signal.rule.type', 'signal.original_event.kind', 'signal.original_event.module', @@ -319,6 +323,15 @@ export const getAlertActions = ({ return module === 'endpoint' && kind === 'alert'; }; + const exceptionsAreAllowed = () => { + const ruleTypes = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.type', + }); + const [ruleType] = ruleTypes as RuleType[]; + return !isMlRule(ruleType) && !isThresholdRule(ruleType); + }; + return [ { ...getInvestigateInResolverAction({ dispatch, timelineId }), @@ -388,7 +401,7 @@ export const getAlertActions = ({ } }, id: 'addException', - isActionDisabled: () => !canUserCRUD || !hasIndexWrite, + isActionDisabled: () => !canUserCRUD || !hasIndexWrite || !exceptionsAreAllowed(), dataTestSubj: 'add-exception-menu-item', ariaLabel: 'Add Exception', content: {i18n.ACTION_ADD_EXCEPTION}, From 133d286639303f08fdfd0507e49724b3122372a2 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 25 Aug 2020 22:27:10 -0600 Subject: [PATCH 13/39] Optimizes the index queries to not block the NodeJS event loop (#75716) (#75942) ## Summary Before this PR you can see event loop block times of: ```ts formatIndexFields: 7986.884ms ``` After this PR you will see event loop block times of: ```ts formatIndexFields: 85.012ms ``` within the file: ```ts x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts ``` For the GraphQL query of `SourceQuery`/`IndexFields` This also fixes the issue of `unknown` being returned to the front end by removing code that is no longer functioning as it was intended. Ensure during testing of this PR that blank/default and non exist indexes within `securitySolution:defaultIndex` still work as expected. Before, notice the `unknown` instead of the `filebeat-*`: Screen Shot 2020-08-20 at 4 55 52 PM After: Screen Shot 2020-08-20 at 4 56 03 PM An explanation of how to see the block times for before and after --- For perf testing you first add timed testing to the file: ```ts x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts ``` Before this PR, around lines 42: ```ts console.time('formatIndexFields'); // <--- start timer const fields = formatIndexFields( responsesIndexFields, Object.keys(indexesAliasIndices) as IndexAlias[] ); console.timeEnd('formatIndexFields'); // <--- outputs the end timer return fields; ``` After this PR, around lines 42: ```ts console.time('formatIndexFields'); // <--- start timer const fields = await formatIndexFields(responsesIndexFields, indices); console.timeEnd('formatIndexFields'); // <--- outputs the end timer return fields; ``` And then reload the security solutions application web page here: ``` http://localhost:5601/app/security/timelines/default ``` Be sure to load it _twice_ for testing as NodeJS will sometimes report better numbers the second time as it does optimizations after the first time it encounters some code paths. You will begin to see numbers similar to this before this PR: ```ts formatIndexFields: 2553.279ms ``` This indicates that it is blocking the event loop for ~2.5 seconds befofe this fix. If you add additional indexes to your `securitySolution:defaultIndex` indexes that have additional fields then this amount will increase exponentially. For developers using our test servers I created two other indexes called delme-1 and delme-2 with additional mappings you can add like below ```ts apm-*-transaction*, auditbeat-*, endgame-*, filebeat-*, logs-*, packetbeat-*, winlogbeat-*, delme-1, delme-2 ``` Screen Shot 2020-08-21 at 8 21 50 PM Then you are going to see times approaching 8 seconds of blocking the event loop like so: ```ts formatIndexFields: 7986.884ms ``` After this fix on the first pass unoptimized it will report ```ts formatIndexFields: 373.082ms ``` Then after it optimizes the code paths on a second page load it will report ```ts formatIndexFields: 84.304ms ``` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios # Conflicts: # x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts # x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts # x-pack/plugins/security_solution/server/utils/beat_schema/index.ts --- .../elasticsearch_adapter.test.ts | 564 +++++++++++++++++- .../lib/index_fields/elasticsearch_adapter.ts | 208 ++++--- .../server/utils/beat_schema/index.test.ts | 15 +- .../server/utils/beat_schema/index.ts | 9 - .../server/utils/beat_schema/type.ts | 2 - 5 files changed, 692 insertions(+), 106 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.test.ts index 20bc1387a3c4e..e8883111c95f6 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.test.ts @@ -6,16 +6,21 @@ import { sortBy } from 'lodash/fp'; -import { formatIndexFields } from './elasticsearch_adapter'; +import { + formatIndexFields, + formatFirstFields, + formatSecondFields, + createFieldItem, +} from './elasticsearch_adapter'; import { mockAuditbeatIndexField, mockFilebeatIndexField, mockPacketbeatIndexField } from './mock'; describe('Index Fields', () => { describe('formatIndexFields', () => { - test('Test Basic functionality', async () => { + test('Basic functionality', async () => { expect( sortBy( 'name', - formatIndexFields( + await formatIndexFields( [mockAuditbeatIndexField, mockFilebeatIndexField, mockPacketbeatIndexField], ['auditbeat', 'filebeat', 'packetbeat'] ) @@ -130,4 +135,557 @@ describe('Index Fields', () => { ); }); }); + + describe('formatFirstFields', () => { + test('Basic functionality', async () => { + const fields = await formatFirstFields( + [mockAuditbeatIndexField, mockFilebeatIndexField, mockPacketbeatIndexField], + ['auditbeat', 'filebeat', 'packetbeat'] + ); + expect(fields).toEqual([ + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + footnote: '', + group: 1, + level: 'core', + name: '_id', + required: true, + type: 'string', + searchable: true, + aggregatable: false, + readFromDocValues: true, + category: '_id', + indexes: ['auditbeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + footnote: '', + group: 1, + level: 'core', + name: '_index', + required: true, + type: 'string', + searchable: true, + aggregatable: true, + readFromDocValues: true, + category: '_index', + indexes: ['auditbeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['auditbeat'], + }, + { + description: + 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + name: 'agent.ephemeral_id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: + 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: + 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + footnote: '', + group: 1, + level: 'core', + name: '_id', + required: true, + type: 'string', + searchable: true, + aggregatable: false, + readFromDocValues: true, + category: '_id', + indexes: ['filebeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + footnote: '', + group: 1, + level: 'core', + name: '_index', + required: true, + type: 'string', + searchable: true, + aggregatable: true, + readFromDocValues: true, + category: '_index', + indexes: ['filebeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['filebeat'], + }, + { + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: + 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + footnote: '', + group: 1, + level: 'core', + name: '_id', + required: true, + type: 'string', + searchable: true, + aggregatable: false, + readFromDocValues: true, + category: '_id', + indexes: ['packetbeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + footnote: '', + group: 1, + level: 'core', + name: '_index', + required: true, + type: 'string', + searchable: true, + aggregatable: true, + readFromDocValues: true, + category: '_index', + indexes: ['packetbeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['packetbeat'], + }, + { + description: + 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + example: '8a4f500d', + name: 'agent.id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['packetbeat'], + }, + { + description: + 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['packetbeat'], + }, + ]); + }); + }); + + describe('formatSecondFields', () => { + test('Basic functionality', async () => { + const fields = await formatSecondFields([ + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + category: '_id', + indexes: ['auditbeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + category: '_index', + indexes: ['auditbeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['auditbeat'], + }, + { + description: + 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + name: 'agent.ephemeral_id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: + 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: + 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + category: '_id', + indexes: ['filebeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + category: '_index', + indexes: ['filebeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['filebeat'], + }, + { + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: + 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + category: '_id', + indexes: ['packetbeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + category: '_index', + indexes: ['packetbeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['packetbeat'], + }, + { + description: + 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + example: '8a4f500d', + name: 'agent.id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['packetbeat'], + }, + { + description: + 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['packetbeat'], + }, + ]); + expect(fields).toEqual([ + { + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + category: '_id', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + }, + { + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + category: '_index', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + }, + { + description: + 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + }, + { + description: + 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + name: 'agent.ephemeral_id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat'], + }, + { + description: + 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat', 'filebeat'], + }, + { + description: + 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat', 'packetbeat'], + }, + { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat', 'filebeat'], + }, + { + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + category: 'agent', + indexes: ['filebeat'], + }, + { + description: + 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + example: '8a4f500d', + name: 'agent.id', + type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['packetbeat'], + }, + ]); + }); + }); + + describe('createFieldItem', () => { + test('Basic functionality', () => { + const item = createFieldItem( + ['auditbeat'], + { + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + }, + 0 + ); + expect(item).toEqual({ + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + footnote: '', + group: 1, + level: 'core', + name: '_id', + required: true, + type: 'string', + searchable: true, + aggregatable: false, + category: '_id', + indexes: ['auditbeat'], + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts index 944fc588afc8a..777b1cf3bb80d 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts @@ -4,50 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty, get } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import { IndexField } from '../../graphql/types'; -import { - baseCategoryFields, - getDocumentation, - getIndexAlias, - hasDocumentation, - IndexAlias, -} from '../../utils/beat_schema'; +import { baseCategoryFields, getDocumentation, hasDocumentation } from '../../utils/beat_schema'; import { FrameworkAdapter, FrameworkRequest } from '../framework'; import { FieldsAdapter, IndexFieldDescriptor } from './types'; -type IndexesAliasIndices = Record; - export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { const indexPatternsService = this.framework.getIndexPatternsService(request); - const indexesAliasIndices: IndexesAliasIndices = indices.reduce( - (accumulator: IndexesAliasIndices, indice: string) => { - const key = getIndexAlias(indices, indice); - - if (get(key, accumulator)) { - accumulator[key] = [...accumulator[key], indice]; - } else { - accumulator[key] = [indice]; - } - return accumulator; - }, - {} as IndexesAliasIndices - ); - const responsesIndexFields: IndexFieldDescriptor[][] = await Promise.all( - Object.values(indexesAliasIndices).map((indicesByGroup) => - indexPatternsService.getFieldsForWildcard({ - pattern: indicesByGroup, - }) - ) - ); - return formatIndexFields( - responsesIndexFields, - Object.keys(indexesAliasIndices) as IndexAlias[] + const responsesIndexFields = await Promise.all( + indices.map((index) => { + return indexPatternsService.getFieldsForWildcard({ + pattern: index, + }); + }) ); + return formatIndexFields(responsesIndexFields, indices); } } @@ -68,51 +43,128 @@ const missingFields = [ }, ]; -export const formatIndexFields = ( +/** + * Creates a single field item. + * + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time calling this function repeatedly. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. + * @param indexesAlias The index alias + * @param index The index its self + * @param indexesAliasIdx The index within the alias + */ +export const createFieldItem = ( + indexesAlias: string[], + index: IndexFieldDescriptor, + indexesAliasIdx: number +): IndexField => { + const alias = indexesAlias[indexesAliasIdx]; + const splitName = index.name.split('.'); + const category = baseCategoryFields.includes(splitName[0]) ? 'base' : splitName[0]; + return { + ...(hasDocumentation(alias, index.name) ? getDocumentation(alias, index.name) : {}), + ...index, + category, + indexes: [alias], + }; +}; + +/** + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time when being called. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. The `.push`, and `forEach` operations are expected within this function + * to speed up performance. + * + * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs + * has already consumed a lot of the event loop processing up to this function and we want to give + * I/O opportunity to occur by scheduling this on the next loop. + * @param responsesIndexFields The response index fields to loop over + * @param indexesAlias The index aliases such as filebeat-* + */ +export const formatFirstFields = async ( responsesIndexFields: IndexFieldDescriptor[][], - indexesAlias: IndexAlias[] -): IndexField[] => - responsesIndexFields - .reduce( - (accumulator: IndexField[], indexFields: IndexFieldDescriptor[], indexesAliasIdx: number) => [ - ...accumulator, - ...[...missingFields, ...indexFields].reduce( - (itemAccumulator: IndexField[], index: IndexFieldDescriptor) => { - const alias: IndexAlias = indexesAlias[indexesAliasIdx]; - const splitName = index.name.split('.'); - const category = baseCategoryFields.includes(splitName[0]) ? 'base' : splitName[0]; - return [ - ...itemAccumulator, - { - ...(hasDocumentation(alias, index.name) ? getDocumentation(alias, index.name) : {}), - ...index, - category, - indexes: [alias], - } as IndexField, - ]; + indexesAlias: string[] +): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + responsesIndexFields.reduce( + ( + accumulator: IndexField[], + indexFields: IndexFieldDescriptor[], + indexesAliasIdx: number + ) => { + missingFields.forEach((index) => { + const item = createFieldItem(indexesAlias, index, indexesAliasIdx); + accumulator.push(item); + }); + indexFields.forEach((index) => { + const item = createFieldItem(indexesAlias, index, indexesAliasIdx); + accumulator.push(item); + }); + return accumulator; }, [] - ), - ], - [] - ) - .reduce((accumulator: IndexField[], indexfield: IndexField) => { - const alreadyExistingIndexField = accumulator.findIndex( - (acc) => acc.name === indexfield.name + ) ); - if (alreadyExistingIndexField > -1) { - const existingIndexField = accumulator[alreadyExistingIndexField]; - return [ - ...accumulator.slice(0, alreadyExistingIndexField), - { - ...existingIndexField, - description: isEmpty(existingIndexField.description) - ? indexfield.description - : existingIndexField.description, - indexes: Array.from(new Set([...existingIndexField.indexes, ...indexfield.indexes])), - }, - ...accumulator.slice(alreadyExistingIndexField + 1), - ]; - } - return [...accumulator, indexfield]; - }, []); + }); + }); +}; + +/** + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time when being called. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. The `.push`, and `forEach` operations are expected within this function + * to speed up performance. The "indexFieldNameHash" side effect hash avoids additional expensive n^2 + * look ups. + * + * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs + * has already consumed a lot of the event loop processing up to this function and we want to give + * I/O opportunity to occur by scheduling this on the next loop. + * @param fields The index fields to create the secondary fields for + */ +export const formatSecondFields = async (fields: IndexField[]): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + const indexFieldNameHash: Record = {}; + const reduced = fields.reduce((accumulator: IndexField[], indexfield: IndexField) => { + const alreadyExistingIndexField = indexFieldNameHash[indexfield.name]; + if (alreadyExistingIndexField != null) { + const existingIndexField = accumulator[alreadyExistingIndexField]; + if (isEmpty(accumulator[alreadyExistingIndexField].description)) { + accumulator[alreadyExistingIndexField].description = indexfield.description; + } + accumulator[alreadyExistingIndexField].indexes = Array.from( + new Set([...existingIndexField.indexes, ...indexfield.indexes]) + ); + return accumulator; + } + accumulator.push(indexfield); + indexFieldNameHash[indexfield.name] = accumulator.length - 1; + return accumulator; + }, []); + resolve(reduced); + }); + }); +}; + +/** + * Formats the index fields into a format the UI wants. + * + * NOTE: This will have array sizes up to 4.7 megs in size at a time when being called. + * This function should be as optimized as possible and should avoid any and all creation + * of new arrays, iterating over the arrays or performing any n^2 operations. + * @param responsesIndexFields The response index fields to format + * @param indexesAlias The index alias + */ +export const formatIndexFields = async ( + responsesIndexFields: IndexFieldDescriptor[][], + indexesAlias: string[] +): Promise => { + const fields = await formatFirstFields(responsesIndexFields, indexesAlias); + const secondFields = await formatSecondFields(fields); + return secondFields; +}; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts index 56ceca2b70e9c..29944edf382f4 100644 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts +++ b/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts @@ -6,7 +6,7 @@ import { cloneDeep, isArray } from 'lodash/fp'; -import { convertSchemaToAssociativeArray, getIndexSchemaDoc, getIndexAlias } from '.'; +import { convertSchemaToAssociativeArray, getIndexSchemaDoc } from '.'; import { auditbeatSchema, filebeatSchema, packetbeatSchema } from './8.0.0'; import { Schema } from './type'; @@ -394,17 +394,4 @@ describe('Schema Beat', () => { ]); }); }); - - describe('getIndexAlias', () => { - test('getIndexAlias handles values with leading wildcard', () => { - const leadingWildcardIndex = '*-auditbeat-*'; - const result = getIndexAlias([leadingWildcardIndex], leadingWildcardIndex); - expect(result).toBe(leadingWildcardIndex); - }); - test('getIndexAlias no match returns "unknown" string', () => { - const index = 'auditbeat-*'; - const result = getIndexAlias([index], 'hello'); - expect(result).toBe('unknown'); - }); - }); }); diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts index ff7331cf39bc7..58627a199a181 100644 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts +++ b/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts @@ -76,15 +76,6 @@ const convertFieldsToAssociativeArray = ( }, {}) : {}; -export const getIndexAlias = (defaultIndex: string[], indexName: string): string => { - const found = defaultIndex.find((index) => `\\${indexName}`.match(`\\${index}`) != null); - if (found != null) { - return found; - } else { - return 'unknown'; - } -}; - export const getIndexSchemaDoc = memoize((index: string) => { if (index.match('auditbeat') != null) { return { diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts index 2b7be8f4b7539..722589ce7e2bb 100644 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts +++ b/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export type IndexAlias = 'auditbeat' | 'filebeat' | 'packetbeat' | 'ecs' | 'winlogbeat' | 'unknown'; - /* * BEAT Interface * From 698794d315d6392e5c4d2b6769dd97d295c7abef Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 26 Aug 2020 13:27:42 -0700 Subject: [PATCH 14/39] [Ingest Manager] Return ID when default output is found (#75930) (#76011) * Return ID when default output is found * Fix typing --- x-pack/plugins/ingest_manager/server/services/output.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/ingest_manager/server/services/output.ts index b4af231024370..1e5632719fb72 100644 --- a/x-pack/plugins/ingest_manager/server/services/output.ts +++ b/x-pack/plugins/ingest_manager/server/services/output.ts @@ -15,7 +15,7 @@ let cachedAdminUser: null | { username: string; password: string } = null; class OutputService { public async getDefaultOutput(soClient: SavedObjectsClientContract) { - return await soClient.find({ + return await soClient.find({ type: OUTPUT_SAVED_OBJECT_TYPE, searchFields: ['is_default'], search: 'true', @@ -42,6 +42,7 @@ class OutputService { } return { + id: outputs.saved_objects[0].id, ...outputs.saved_objects[0].attributes, }; } From b737548f9a989fa37d260b15bed7bfd3ef23e923 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Wed, 26 Aug 2020 15:11:04 -0600 Subject: [PATCH 15/39] Fixes runtime error with meta when it is missing (#75844) (#75992) ## Summary Found in 7.9.0, if you post a rule with an action that has a missing "meta" then you are going to get errors in your UI that look something like: ```ts An error occurred during rule execution: message: "Cannot read property 'kibana_siem_app_url' of null" name: "Unusual Windows Remote User" id: "1cc27e7e-d7c7-4f6a-b918-8c272fc6b1a3" rule id: "1781d055-5c66-4adf-9e93-fc0fa69550c9" signals index: ".siem-signals-default" ``` This fixes the accidental referencing of the null/undefined property and adds both integration and unit tests in that area of code. If you have an action id handy you can manually test this by editing the json file of: ```ts test_cases/queries/action_without_meta.json ``` to have your action id and then posting it like so: ```ts ./post_rule.sh ./rules/test_cases/queries/action_without_meta.json ``` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../rules_notification_alert_type.test.ts | 78 ++++++++++ .../rules_notification_alert_type.ts | 4 +- .../queries/action_without_meta.json | 42 ++++++ .../signals/signal_rule_alert_type.test.ts | 100 +++++++++++++ .../signals/signal_rule_alert_type.ts | 3 +- .../security_and_spaces/tests/add_actions.ts | 134 ++++++++++++++++++ .../security_and_spaces/tests/index.ts | 1 + .../detection_engine_api_integration/utils.ts | 43 ++++++ 8 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts index 3eefd3e665cd6..593ada470b118 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts @@ -79,6 +79,84 @@ describe('rules_notification_alert_type', () => { ); }); + it('should resolve results_link when meta is undefined to use "/app/security"', async () => { + const ruleAlert = getResult(); + delete ruleAlert.params.meta; + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + alertServices.callCluster.mockResolvedValue({ + count: 10, + }); + + await alert.executor(payload); + expect(alertServices.alertInstanceFactory).toHaveBeenCalled(); + + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledWith( + 'default', + expect.objectContaining({ + results_link: + '/app/security/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:1576255233400,kind:absolute,to:1576341633400)),timeline:(linkTo:!(global),timerange:(from:1576255233400,kind:absolute,to:1576341633400)))', + }) + ); + }); + + it('should resolve results_link when meta is an empty object to use "/app/security"', async () => { + const ruleAlert = getResult(); + ruleAlert.params.meta = {}; + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + alertServices.callCluster.mockResolvedValue({ + count: 10, + }); + await alert.executor(payload); + expect(alertServices.alertInstanceFactory).toHaveBeenCalled(); + + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledWith( + 'default', + expect.objectContaining({ + results_link: + '/app/security/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:1576255233400,kind:absolute,to:1576341633400)),timeline:(linkTo:!(global),timerange:(from:1576255233400,kind:absolute,to:1576341633400)))', + }) + ); + }); + + it('should resolve results_link to custom kibana link when given one', async () => { + const ruleAlert = getResult(); + ruleAlert.params.meta = { + kibana_siem_app_url: 'http://localhost', + }; + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + alertServices.callCluster.mockResolvedValue({ + count: 10, + }); + await alert.executor(payload); + expect(alertServices.alertInstanceFactory).toHaveBeenCalled(); + + const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; + expect(alertInstanceMock.scheduleActions).toHaveBeenCalledWith( + 'default', + expect.objectContaining({ + results_link: + 'http://localhost/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:1576255233400,kind:absolute,to:1576341633400)),timeline:(linkTo:!(global),timerange:(from:1576255233400,kind:absolute,to:1576341633400)))', + }) + ); + }); + it('should not call alertInstanceFactory if signalsCount was 0', async () => { const ruleAlert = getResult(); alertServices.savedObjectsClient.get.mockResolvedValue({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index ab824957087fc..2eb34529d044c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -64,8 +64,8 @@ export const rulesNotificationAlertType = ({ from: fromInMs, to: toInMs, id: ruleAlertSavedObject.id, - kibanaSiemAppUrl: (ruleAlertParams.meta as { kibana_siem_app_url?: string }) - .kibana_siem_app_url, + kibanaSiemAppUrl: (ruleAlertParams.meta as { kibana_siem_app_url?: string } | undefined) + ?.kibana_siem_app_url, }); logger.info( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json new file mode 100644 index 0000000000000..6569a641de3a2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json @@ -0,0 +1,42 @@ +{ + "type": "query", + "index": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "filters": [], + "language": "kuery", + "query": "host.name: *", + "author": [], + "false_positives": [], + "references": [], + "risk_score": 50, + "risk_score_mapping": [], + "severity": "low", + "severity_mapping": [], + "threat": [], + "name": "Host Name Test", + "description": "Host Name Test", + "tags": [], + "license": "", + "interval": "5m", + "from": "now-30s", + "to": "now", + "actions": [ + { + "group": "default", + "id": "4c42ecf2-5e9b-4ce6-8a7a-ab620fd8b169", + "params": { + "body": "{}" + }, + "action_type_id": ".webhook" + } + ], + "enabled": true, + "throttle": "rule" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index b0c855afa8be9..b29d15f5e5c72 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -16,6 +16,7 @@ import { getListsClient, getExceptions, sortExceptionItems, + parseScheduleDates, } from './utils'; import { RuleExecutorOptions } from './types'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; @@ -227,6 +228,105 @@ describe('rules_notification_alert_type', () => { ); }); + it('should resolve results_link when meta is an empty object to use "/app/security"', async () => { + const ruleAlert = getResult(); + ruleAlert.params.meta = {}; + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + (parseScheduleDates as jest.Mock).mockReturnValue(moment(100)); + payload.params.meta = {}; + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + resultsLink: + '/app/security/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))', + }) + ); + }); + + it('should resolve results_link when meta is undefined use "/app/security"', async () => { + const ruleAlert = getResult(); + delete ruleAlert.params.meta; + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + (parseScheduleDates as jest.Mock).mockReturnValue(moment(100)); + delete payload.params.meta; + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + resultsLink: + '/app/security/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))', + }) + ); + }); + + it('should resolve results_link with a custom link', async () => { + const ruleAlert = getResult(); + ruleAlert.params.meta = { kibana_siem_app_url: 'http://localhost' }; + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + alertServices.savedObjectsClient.get.mockResolvedValue({ + id: 'rule-id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + (parseScheduleDates as jest.Mock).mockReturnValue(moment(100)); + payload.params.meta = { kibana_siem_app_url: 'http://localhost' }; + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + resultsLink: + 'http://localhost/detections/rules/id/rule-id?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))', + }) + ); + }); + describe('ML rule', () => { it('should throw an error if ML plugin was not available', async () => { const ruleAlert = getMlResult(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 0e859ecef31c6..b5cbf80b084f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -356,7 +356,8 @@ export const signalRulesAlertType = ({ from: fromInMs, to: toInMs, id: savedObject.id, - kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string }).kibana_siem_app_url, + kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) + ?.kibana_siem_app_url, }); logger.info( diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts new file mode 100644 index 0000000000000..2468851237047 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + removeServerGeneratedProperties, + waitFor, + getWebHookAction, + getRuleWithWebHookAction, + getSimpleRuleOutputWithWebHookAction, +} from '../../utils'; +import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('add_actions', () => { + describe('adding actions', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + }); + + it('should be able to create a new webhook action and attach it to a rule', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule with the action attached + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(getRuleWithWebHookAction(hookAction.id)) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare).to.eql( + getSimpleRuleOutputWithWebHookAction(`${bodyToCompare?.actions?.[0].id}`) + ); + }); + + it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule with the action attached + const { body: rule } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(getRuleWithWebHookAction(hookAction.id)) + .expect(200); + + // wait for Task Manager to execute the rule and its update status + await waitFor(async () => { + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [rule.id] }) + .expect(200); + return body[rule.id].current_status?.status === 'succeeded'; + }); + + // expected result for status should be 'succeeded' + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [rule.id] }) + .expect(200); + expect(body[rule.id].current_status.status).to.eql('succeeded'); + }); + + it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule with the action attached and a meta field + const ruleWithAction: CreateRulesSchema = { + ...getRuleWithWebHookAction(hookAction.id), + meta: {}, + }; + + const { body: rule } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(ruleWithAction) + .expect(200); + + // wait for Task Manager to execute the rule and update status + await waitFor(async () => { + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [rule.id] }) + .expect(200); + return body[rule.id].current_status?.status === 'succeeded'; + }); + + // expected result for status should be 'succeeded' + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [rule.id] }) + .expect(200); + expect(body[rule.id].current_status.status).to.eql('succeeded'); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index a480e63ff4a92..779205377621d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -11,6 +11,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { describe('detection engine api security and spaces enabled', function () { this.tags('ciGroup1'); + loadTestFile(require.resolve('./add_actions')); loadTestFile(require.resolve('./add_prepackaged_rules')); loadTestFile(require.resolve('./create_rules')); loadTestFile(require.resolve('./create_rules_bulk')); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 102a1577a7eaf..d801b38ba9f53 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -552,6 +552,49 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => exceptions_list: [], }); +export const getWebHookAction = () => ({ + actionTypeId: '.webhook', + config: { + method: 'post', + url: 'http://localhost', + }, + secrets: { + user: 'example', + password: 'example', + }, + name: 'Some connector', +}); + +export const getRuleWithWebHookAction = (id: string): CreateRulesSchema => ({ + ...getSimpleRule(), + throttle: 'rule', + actions: [ + { + group: 'default', + id, + params: { + body: '{}', + }, + action_type_id: '.webhook', + }, + ], +}); + +export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial => ({ + ...getSimpleRuleOutput(), + throttle: 'rule', + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: actionId, + params: { + body: '{}', + }, + }, + ], +}); + // Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor export const waitFor = async ( functionToTest: () => Promise, From 416235a07520dffcfcd8aad17f3fcd42c0730b49 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Wed, 26 Aug 2020 15:55:08 -0600 Subject: [PATCH 16/39] [Security Solution][Detections] Fixes Alerts Table 'Select all [x] alerts' action (#75945) (#76027) ## Summary Resolves https://github.com/elastic/kibana/issues/75194 Fixes issue where the `Select all [x] alerts` feature would not select the checkboxes within the Alerts Table. Also resolves issue where bulk actions wouldn't work with Building Block Alerts. ##### Select All Before

##### Select All After

##### Building Block Query Before

##### Building Block Query After

--- .../components/alerts_table/index.tsx | 42 ++++++++++++------- .../components/manage_timeline/index.tsx | 24 +++++++++++ .../timeline/body/stateful_body.tsx | 4 +- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 66423259ec155..07e69d850f173 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -105,7 +105,6 @@ export const AlertsTableComponent: React.FC = ({ updateTimelineIsLoading, }) => { const dispatch = useDispatch(); - const [selectAll, setSelectAll] = useState(false); const apolloClient = useApolloClient(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); @@ -120,6 +119,12 @@ export const AlertsTableComponent: React.FC = ({ ); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); + const { + initializeTimeline, + setSelectAll, + setTimelineRowActions, + setIndexToAdd, + } = useManageTimeline(); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -141,8 +146,7 @@ export const AlertsTableComponent: React.FC = ({ } return null; }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [browserFields, globalFilters, globalQuery, indexPatterns, kibana, to, from] + [browserFields, defaultFilters, globalFilters, globalQuery, indexPatterns, kibana, to, from] ); // Callback for creating a new timeline -- utilized by row/batch actions @@ -240,12 +244,15 @@ export const AlertsTableComponent: React.FC = ({ // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar useEffect(() => { - if (!isSelectAllChecked) { - setShowClearSelectionAction(false); + if (isSelectAllChecked) { + setSelectAll({ + id: timelineId, + selectAll: false, + }); } else { - setSelectAll(false); + setShowClearSelectionAction(false); } - }, [isSelectAllChecked]); + }, [isSelectAllChecked, setSelectAll, timelineId]); // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( @@ -261,17 +268,23 @@ export const AlertsTableComponent: React.FC = ({ // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { clearSelected!({ id: timelineId }); - setSelectAll(false); + setSelectAll({ + id: timelineId, + selectAll: false, + }); setShowClearSelectionAction(false); }, [clearSelected, setSelectAll, setShowClearSelectionAction, timelineId]); // Callback for selecting all events on all pages from utility bar // Dispatches to stateful_body's selectAll via TimelineTypeContext props // as scope of response data required to actually set selectedEvents - const selectAllCallback = useCallback(() => { - setSelectAll(true); + const selectAllOnAllPagesCallback = useCallback(() => { + setSelectAll({ + id: timelineId, + selectAll: true, + }); setShowClearSelectionAction(true); - }, [setSelectAll, setShowClearSelectionAction]); + }, [setSelectAll, setShowClearSelectionAction, timelineId]); const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( async ( @@ -314,7 +327,7 @@ export const AlertsTableComponent: React.FC = ({ clearSelection={clearSelectionCallback} hasIndexWrite={hasIndexWrite} currentFilter={filterGroup} - selectAll={selectAllCallback} + selectAll={selectAllOnAllPagesCallback} selectedEventIds={selectedEventIds} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChanged} @@ -332,7 +345,7 @@ export const AlertsTableComponent: React.FC = ({ showBuildingBlockAlerts, onShowBuildingBlockAlertsChanged, loadingEventIds.length, - selectAllCallback, + selectAllOnAllPagesCallback, selectedEventIds, showClearSelectionAction, updateAlertsStatusCallback, @@ -384,7 +397,6 @@ export const AlertsTableComponent: React.FC = ({ } }, [defaultFilters, filterGroup]); const { filterManager } = useKibana().services.data.query; - const { initializeTimeline, setTimelineRowActions, setIndexToAdd } = useManageTimeline(); useEffect(() => { initializeTimeline({ @@ -395,7 +407,7 @@ export const AlertsTableComponent: React.FC = ({ id: timelineId, indexToAdd: defaultIndices, loadingText: i18n.LOADING_ALERTS, - selectAll: canUserCRUD ? selectAll : false, + selectAll: false, timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })], title: '', }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index a425f9b49add0..560d4c6928e4e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -71,6 +71,11 @@ type ActionManageTimeline = id: string; payload: string[]; } + | { + type: 'SET_SELECT_ALL'; + id: string; + payload: boolean; + } | { type: 'SET_TIMELINE_ACTIONS'; id: string; @@ -116,6 +121,14 @@ const reducerManageTimeline = ( indexToAdd: action.payload, }, } as ManageTimelineById; + case 'SET_SELECT_ALL': + return { + ...state, + [action.id]: { + ...state[action.id], + selectAll: action.payload, + }, + } as ManageTimelineById; case 'SET_TIMELINE_ACTIONS': return { ...state, @@ -145,6 +158,7 @@ export interface UseTimelineManager { isManagedTimeline: (id: string) => boolean; setIndexToAdd: (indexToAddArgs: { id: string; indexToAdd: string[] }) => void; setIsTimelineLoading: (isLoadingArgs: { id: string; isLoading: boolean }) => void; + setSelectAll: (selectAllArgs: { id: string; selectAll: boolean }) => void; setTimelineRowActions: (actionsArgs: { id: string; queryFields?: string[]; @@ -205,6 +219,14 @@ export const useTimelineManager = ( }); }, []); + const setSelectAll = useCallback(({ id, selectAll }: { id: string; selectAll: boolean }) => { + dispatch({ + type: 'SET_SELECT_ALL', + id, + payload: selectAll, + }); + }, []); + const getTimelineFilterManager = useCallback( (id: string): FilterManager | undefined => state[id]?.filterManager, [state] @@ -238,6 +260,7 @@ export const useTimelineManager = ( isManagedTimeline, setIndexToAdd, setIsTimelineLoading, + setSelectAll, setTimelineRowActions, }; }; @@ -250,6 +273,7 @@ const init = { isManagedTimeline: () => false, setIndexToAdd: () => undefined, setIsTimelineLoading: () => noop, + setSelectAll: () => noop, setTimelineRowActions: () => noop, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx index 15fa13b1a08f1..8deda03ece70e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx @@ -169,10 +169,10 @@ const StatefulBodyComponent = React.memo( // Sync to selectAll so parent components can select all events useEffect(() => { - if (selectAll) { + if (selectAll && !isSelectAllChecked) { onSelectAll({ isSelected: true }); } - }, [onSelectAll, selectAll]); + }, [isSelectAllChecked, onSelectAll, selectAll]); const enabledRowRenderers = useMemo(() => { if ( From 0b688f7c84aa94aa237c909ffa6fe65b70d6493b Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 26 Aug 2020 17:17:50 -0700 Subject: [PATCH 17/39] Disables Chromedriver version detection (#75984) (#76044) Signed-off-by: Tyler Smalley --- src/dev/ci_setup/setup.sh | 6 ++++++ src/dev/ci_setup/setup_env.sh | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/dev/ci_setup/setup.sh b/src/dev/ci_setup/setup.sh index aabc1e75b9025..3351170c29e01 100755 --- a/src/dev/ci_setup/setup.sh +++ b/src/dev/ci_setup/setup.sh @@ -16,6 +16,12 @@ echo " -- TEST_ES_SNAPSHOT_VERSION='$TEST_ES_SNAPSHOT_VERSION'" echo " -- installing node.js dependencies" yarn kbn bootstrap --prefer-offline +### +### ensure Chromedriver install hook is triggered +### when modules are up-to-date +### +node node_modules/chromedriver/install.js + ### ### Download es snapshots ### diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index f96a2240917e2..d353ac400d561 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -133,13 +133,13 @@ export CYPRESS_DOWNLOAD_MIRROR="https://us-central1-elastic-kibana-184716.cloudf export CHECKS_REPORTER_ACTIVE=false # This is mainly for release-manager builds, which run in an environment that doesn't have Chrome installed -if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then - echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" - export DETECT_CHROMEDRIVER_VERSION=true - export CHROMEDRIVER_FORCE_DOWNLOAD=true -else - echo "Chrome not detected, installing default chromedriver binary for the package version" -fi +# if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then +# echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" +# export DETECT_CHROMEDRIVER_VERSION=true +# export CHROMEDRIVER_FORCE_DOWNLOAD=true +# else +# echo "Chrome not detected, installing default chromedriver binary for the package version" +# fi ### only run on pr jobs for elastic/kibana, checks-reporter doesn't work for other repos if [[ "$ghprbPullId" && "$ghprbGhRepository" == 'elastic/kibana' ]] ; then From 802ef8e2605d16596f2d93f08e9d767c044ecadf Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 27 Aug 2020 06:43:24 +0100 Subject: [PATCH 18/39] [ML] Adding authorization header to DFA job update request (#75899) (#76020) # Conflicts: # x-pack/plugins/ml/server/routes/data_frame_analytics.ts --- x-pack/plugins/ml/server/routes/data_frame_analytics.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 94feb21a6b5fb..430d738793f4c 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -518,6 +518,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat { body: request.body, analyticsId, + ...getAuthorizationHeader(request), } ); return response.ok({ From c4722ca2680d013cc7fc97a83d68523d523b9447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:25:41 +0100 Subject: [PATCH 19/39] [APM] User can't navigate back home using browser nav when clicking link (#75755) (#75986) * replaces the route when parmeter is missing * fixing unit test Co-authored-by: Elastic Machine --- .../shared/DatePicker/__test__/DatePicker.test.tsx | 14 ++++++++------ .../public/components/shared/DatePicker/index.tsx | 9 ++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx index d4eeaf8b290ea..a6e23d049cd7b 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx @@ -20,6 +20,7 @@ import { wait } from '@testing-library/react'; import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; const mockHistoryPush = jest.spyOn(history, 'push'); +const mockHistoryReplace = jest.spyOn(history, 'replace'); const mockRefreshTimeRange = jest.fn(); const MockUrlParamsProvider: React.FC<{ params?: IUrlParams; @@ -63,8 +64,8 @@ describe('DatePicker', () => { it('sets default query params in the URL', () => { mountDatePicker(); - expect(mockHistoryPush).toHaveBeenCalledTimes(1); - expect(mockHistoryPush).toHaveBeenCalledWith( + expect(mockHistoryReplace).toHaveBeenCalledTimes(1); + expect(mockHistoryReplace).toHaveBeenCalledWith( expect.objectContaining({ search: 'rangeFrom=now-15m&rangeTo=now', }) @@ -76,8 +77,8 @@ describe('DatePicker', () => { rangeTo: 'now', refreshInterval: 5000, }); - expect(mockHistoryPush).toHaveBeenCalledTimes(1); - expect(mockHistoryPush).toHaveBeenCalledWith( + expect(mockHistoryReplace).toHaveBeenCalledTimes(1); + expect(mockHistoryReplace).toHaveBeenCalledWith( expect.objectContaining({ search: 'rangeFrom=now-15m&rangeTo=now&refreshInterval=5000', }) @@ -91,18 +92,19 @@ describe('DatePicker', () => { refreshPaused: false, refreshInterval: 5000, }); - expect(mockHistoryPush).toHaveBeenCalledTimes(0); + expect(mockHistoryReplace).toHaveBeenCalledTimes(0); }); it('updates the URL when the date range changes', () => { const datePicker = mountDatePicker(); + expect(mockHistoryReplace).toHaveBeenCalledTimes(1); datePicker.find(EuiSuperDatePicker).props().onTimeChange({ start: 'updated-start', end: 'updated-end', isInvalid: false, isQuickSelection: true, }); - expect(mockHistoryPush).toHaveBeenCalledTimes(2); + expect(mockHistoryPush).toHaveBeenCalledTimes(1); expect(mockHistoryPush).toHaveBeenLastCalledWith( expect.objectContaining({ search: 'rangeFrom=updated-start&rangeTo=updated-end', diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx index 403a8cad854cd..35b9525733e99 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx @@ -89,7 +89,14 @@ export function DatePicker() { ...timePickerURLParams, }; if (!isEqual(nextParams, timePickerURLParams)) { - updateUrl(nextParams); + // When the default parameters are not availbale in the url, replace it adding the necessary parameters. + history.replace({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + ...nextParams, + }), + }); } return ( From 3512f5727d75304b65557036f3908c57d53c0aca Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 27 Aug 2020 13:05:20 +0200 Subject: [PATCH 20/39] [ML] fix tooltip content for scheduled events (#75973) (#76067) --- .../application/components/chart_tooltip/chart_tooltip.tsx | 7 ++++++- .../explorer_charts/explorer_chart_single_metric.js | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx index 3bd4ae1748311..d251647d2a540 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx @@ -72,6 +72,11 @@ const Tooltip: FC<{ service: ChartTooltipService }> = React.memo(({ service }) = /* eslint @typescript-eslint/camelcase:0 */ echTooltip__rowHighlighted: isHighlighted, }); + + const renderValue = Array.isArray(value) + ? value.map((v) =>
{v}
) + : value; + return (
= React.memo(({ service }) = {label} - {value} + {renderValue}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 63775c5ca312e..7cfa7bd6addbd 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -37,7 +37,6 @@ import { } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; import { getTimeBucketsFromCache } from '../../util/time_buckets'; -import { mlEscape } from '../../util/string_utils'; import { mlFieldFormatService } from '../../services/field_format_service'; import { i18n } from '@kbn/i18n'; @@ -488,7 +487,7 @@ export class ExplorerChartSingleMetric extends React.Component { label: i18n.translate('xpack.ml.explorer.singleMetricChart.scheduledEventsLabel', { defaultMessage: 'Scheduled events', }), - value: marker.scheduledEvents.map(mlEscape).join('
'), + value: marker.scheduledEvents, seriesIdentifier: { key: seriesKey, }, From f3d36a1423b0f12d9383d7cf0ec1811c67e14474 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Thu, 27 Aug 2020 09:32:02 -0400 Subject: [PATCH 21/39] [7.9] [Security Solution] [Detections] Updates rules routes to validate "from" param on rules (#76000) (#76048) * updates validation on 'from' param to prevent malformed datemath strings from being accepted * fix imports * copy paste is not my friend * missed type check somehow * forgot to mock common utils * updates bodies for request validation tests --- .../schemas/common/schemas.ts | 17 ++++++++- .../schemas/types/default_from_string.ts | 10 +++-- .../common/detection_engine/utils.ts | 15 ++++++++ .../rules_notification_alert_type.ts | 2 +- .../rules/create_rules_bulk_route.test.ts | 27 ++++++++++++++ .../routes/rules/create_rules_route.test.ts | 25 +++++++++++++ .../rules/patch_rules_bulk_route.test.ts | 27 ++++++++++++++ .../routes/rules/patch_rules_route.test.ts | 33 +++++++++++++++-- .../rules/update_rules_bulk_route.test.ts | 28 ++++++++++++++ .../routes/rules/update_rules_route.test.ts | 34 +++++++++++++++-- .../signals/signal_rule_alert_type.test.ts | 3 +- .../signals/signal_rule_alert_type.ts | 2 +- .../detection_engine/signals/utils.test.ts | 2 +- .../lib/detection_engine/signals/utils.ts | 14 +------ .../basic/tests/import_rules.ts | 37 +++++++++++++++++++ 15 files changed, 246 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 21fa780badd84..9b5bf992ae6c5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -7,11 +7,14 @@ /* eslint-disable @typescript-eslint/camelcase */ import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; + import { RiskScore } from '../types/risk_score'; import { UUID } from '../types/uuid'; import { IsoDateString } from '../types/iso_date_string'; import { PositiveIntegerGreaterThanZero } from '../types/positive_integer_greater_than_zero'; import { PositiveInteger } from '../types/positive_integer'; +import { parseScheduleDates } from '../../utils'; export const author = t.array(t.string); export type Author = t.TypeOf; @@ -76,8 +79,18 @@ export const action = t.exact( export const actions = t.array(action); export type Actions = t.TypeOf; -// TODO: Create a regular expression type or custom date math part type here -export const from = t.string; +const stringValidator = (input: unknown): input is string => typeof input === 'string'; +export const from = new t.Type( + 'From', + t.string.is, + (input, context): Either => { + if (stringValidator(input) && parseScheduleDates(input) == null) { + return t.failure(input, context, 'Failed to parse "from" on rule param'); + } + return t.string.validate(input, context); + }, + t.identity +); export type From = t.TypeOf; export const fromOrUndefined = t.union([from, t.undefined]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts index a85ea58b26478..5b1c837db9f74 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_from_string.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; - +import { from } from '../common/schemas'; /** * Types the DefaultFromString as: * - If null or undefined, then a default of the string "now-6m" will be used @@ -14,7 +14,11 @@ import { Either } from 'fp-ts/lib/Either'; export const DefaultFromString = new t.Type( 'DefaultFromString', t.string.is, - (input, context): Either => - input == null ? t.success('now-6m') : t.string.validate(input, context), + (input, context): Either => { + if (input == null) { + return t.success('now-6m'); + } + return from.validate(input, context); + }, t.identity ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index 153130fc16d60..a70258c2684b6 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; +import dateMath from '@elastic/datemath'; + import { EntriesArray } from '../shared_imports'; import { RuleType } from './types'; @@ -18,3 +21,15 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { }; export const isThresholdRule = (ruleType: RuleType) => ruleType === 'threshold'; + +export const parseScheduleDates = (time: string): moment.Moment | null => { + const isValidDateString = !isNaN(Date.parse(time)); + const isValidInput = isValidDateString || time.trim().startsWith('now'); + const formattedDate = isValidDateString + ? moment(time) + : isValidInput + ? dateMath.parse(time) + : null; + + return formattedDate ?? null; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index 2eb34529d044c..0a899562d61c2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -14,7 +14,7 @@ import { RuleAlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; -import { parseScheduleDates } from '../signals/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/utils'; export const rulesNotificationAlertType = ({ logger, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 4636618cc5ac0..06fcba36642ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -161,6 +161,17 @@ describe('create_rules_bulk', () => { expect(result.ok).toHaveBeenCalled(); }); + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'post', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }], + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + test('disallows unknown rule type', async () => { const request = requestMock.create({ method: 'post', @@ -173,5 +184,21 @@ describe('create_rules_bulk', () => { 'Invalid value "unexpected_type" supplied to "type"' ); }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'post', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + body: [ + { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getCreateRulesSchemaMock(), + }, + ], + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 59c64fbf8fce1..26febb0999ac7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -164,5 +164,30 @@ describe('create_rules', () => { 'Invalid value "unexpected_type" supplied to "type"' ); }); + + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_RULES_URL, + body: { from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }, + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_RULES_URL, + body: { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getCreateRulesSchemaMock(), + }, + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index db32f7f4485b1..c162caa1278e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -183,5 +183,32 @@ describe('patch_rules_bulk', () => { 'Invalid value "unknown_type" supplied to "type"' ); }); + + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'patch', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }], + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'patch', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + body: [ + { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getCreateRulesSchemaMock(), + }, + ], + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index d3350bcb0d762..a406de593652b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -18,7 +18,7 @@ import { } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { patchRulesRoute } from './patch_rules_route'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; +import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -156,7 +156,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...getCreateRulesSchemaMock(), rule_id: undefined }, + body: { ...getPatchRulesSchemaMock(), rule_id: undefined }, }); const response = await server.inject(request, context); expect(response.body).toEqual({ @@ -169,7 +169,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...getCreateRulesSchemaMock(), type: 'query' }, + body: { ...getPatchRulesSchemaMock(), type: 'query' }, }); const result = server.validate(request); @@ -180,7 +180,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...getCreateRulesSchemaMock(), type: 'unknown_type' }, + body: { ...getPatchRulesSchemaMock(), type: 'unknown_type' }, }); const result = server.validate(request); @@ -188,5 +188,30 @@ describe('patch_rules', () => { 'Invalid value "unknown_type" supplied to "type"' ); }); + + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'patch', + path: DETECTION_ENGINE_RULES_URL, + body: { from: 'now-7m', interval: '5m', ...getPatchRulesSchemaMock() }, + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'patch', + path: DETECTION_ENGINE_RULES_URL, + body: { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getPatchRulesSchemaMock(), + }, + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts index 9c5df89a52bed..ec5a2be255a2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts @@ -154,5 +154,33 @@ describe('update_rules_bulk', () => { 'Invalid value "unknown_type" supplied to "type"' ); }); + + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'put', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock(), type: 'query' }], + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'put', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + body: [ + { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getCreateRulesSchemaMock(), + type: 'query', + }, + ], + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index 46fe773e1a88d..fd077c18b7983 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -19,7 +19,7 @@ import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { updateRulesRoute } from './update_rules_route'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; +import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/update_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); jest.mock('../../rules/update_rules_notifications'); @@ -130,7 +130,7 @@ describe('update_rules', () => { method: 'put', path: DETECTION_ENGINE_RULES_URL, body: { - ...getCreateRulesSchemaMock(), + ...getUpdateRulesSchemaMock(), rule_id: undefined, }, }); @@ -145,7 +145,7 @@ describe('update_rules', () => { const request = requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_URL, - body: { ...getCreateRulesSchemaMock(), type: 'query' }, + body: { ...getUpdateRulesSchemaMock(), type: 'query' }, }); const result = await server.validate(request); @@ -156,7 +156,7 @@ describe('update_rules', () => { const request = requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_URL, - body: { ...getCreateRulesSchemaMock(), type: 'unknown type' }, + body: { ...getUpdateRulesSchemaMock(), type: 'unknown type' }, }); const result = await server.validate(request); @@ -164,5 +164,31 @@ describe('update_rules', () => { 'Invalid value "unknown type" supplied to "type"' ); }); + + test('allows rule type of query and custom from and interval', async () => { + const request = requestMock.create({ + method: 'put', + path: DETECTION_ENGINE_RULES_URL, + body: { from: 'now-7m', interval: '5m', ...getUpdateRulesSchemaMock(), type: 'query' }, + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + test('disallows invalid "from" param on rule', async () => { + const request = requestMock.create({ + method: 'put', + path: DETECTION_ENGINE_RULES_URL, + body: { + from: 'now-3755555555555555.67s', + interval: '5m', + ...getUpdateRulesSchemaMock(), + type: 'query', + }, + }); + const result = server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith('Failed to parse "from" on rule param'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index b29d15f5e5c72..a7213c30eb3fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -16,8 +16,8 @@ import { getListsClient, getExceptions, sortExceptionItems, - parseScheduleDates, } from './utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/utils'; import { RuleExecutorOptions } from './types'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; @@ -37,6 +37,7 @@ jest.mock('./utils'); jest.mock('../notifications/schedule_notification_actions'); jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); +jest.mock('./../../../../common/detection_engine/utils'); const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({ alertId: ruleAlert.id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index b5cbf80b084f7..c5124edcaf187 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -14,6 +14,7 @@ import { SERVER_APP_ID, } from '../../../../common/constants'; import { isJobStarted, isMlRule } from '../../../../common/machine_learning/helpers'; +import { parseScheduleDates } from '../../../../common/detection_engine/utils'; import { SetupPlugins } from '../../../plugin'; import { getInputIndex } from './get_input_output_index'; import { @@ -24,7 +25,6 @@ import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition, RuleAlertAttributes } from './types'; import { getGapBetweenRuns, - parseScheduleDates, getListsClient, getExceptions, getGapMaxCatchupRatio, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 3c41f29625a51..a2e2fec3309c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -13,11 +13,11 @@ import { buildRuleMessageFactory } from './rule_messages'; import { ExceptionListClient } from '../../../../../lists/server'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; +import { parseScheduleDates } from '../../../../common/detection_engine/utils'; import { generateId, parseInterval, - parseScheduleDates, getDriftTolerance, getGapBetweenRuns, getGapMaxCatchupRatio, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 9519720d0bbec..92cc9be69839f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -14,7 +14,7 @@ import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; import { ListArrayOrUndefined } from '../../../../common/detection_engine/schemas/types/lists'; import { BulkResponse, BulkResponseErrorAggregation, isValidUnit } from './types'; import { BuildRuleMessage } from './rule_messages'; -import { hasLargeValueList } from '../../../../common/detection_engine/utils'; +import { hasLargeValueList, parseScheduleDates } from '../../../../common/detection_engine/utils'; import { MAX_EXCEPTION_LIST_SIZE } from '../../../../../lists/common/constants'; interface SortExceptionsReturn { @@ -220,18 +220,6 @@ export const parseInterval = (intervalString: string): moment.Duration | null => } }; -export const parseScheduleDates = (time: string): moment.Moment | null => { - const isValidDateString = !isNaN(Date.parse(time)); - const isValidInput = isValidDateString || time.trim().startsWith('now'); - const formattedDate = isValidDateString - ? moment(time) - : isValidInput - ? dateMath.parse(time) - : null; - - return formattedDate ?? null; -}; - export const getDriftTolerance = ({ from, to, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts index e0b60ae1fbeeb..108ca365bc14f 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts @@ -141,6 +141,43 @@ export default ({ getService }: FtrProviderContext): void => { expect(bodyToCompare).to.eql(getSimpleRuleOutput('rule-1')); }); + it('should fail validation when importing a rule with malformed "from" params on the rules', async () => { + const stringifiedRule = JSON.stringify({ + from: 'now-3755555555555555.67s', + interval: '5m', + ...getSimpleRule('rule-1'), + }); + const fileNdJson = Buffer.from(stringifiedRule + '\n'); + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', fileNdJson, 'rules.ndjson') + .expect(200); + + expect(body.errors[0].error.message).to.eql('Failed to parse "from" on rule param'); + }); + + it('should fail validation when importing two rules and one has a malformed "from" params', async () => { + const stringifiedRule = JSON.stringify({ + from: 'now-3755555555555555.67s', + interval: '5m', + ...getSimpleRule('rule-1'), + }); + const stringifiedRule2 = JSON.stringify({ + ...getSimpleRule('rule-2'), + }); + const fileNdJson = Buffer.from([stringifiedRule, stringifiedRule2].join('\n')); + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', fileNdJson, 'rules.ndjson') + .expect(200); + + // should result in one success and a failure message + expect(body.success_count).to.eql(1); + expect(body.errors[0].error.message).to.eql('Failed to parse "from" on rule param'); + }); + it('should be able to import two rules', async () => { const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) From 8c84b8b415a5e068ea6eb1d5cd9eaae3b576fdd2 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 27 Aug 2020 11:08:24 -0400 Subject: [PATCH 22/39] [Docs] Add `server.xsrf.disableProtection` to settings docs (#76022) (#76097) --- docs/api/using-api.asciidoc | 6 ++---- docs/apm/api.asciidoc | 4 ++-- docs/setup/settings.asciidoc | 5 ++++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/api/using-api.asciidoc b/docs/api/using-api.asciidoc index 2e364ce6d9460..50dd8ad44d038 100644 --- a/docs/api/using-api.asciidoc +++ b/docs/api/using-api.asciidoc @@ -45,10 +45,8 @@ For all APIs, you must use a request header. The {kib} APIs support the `kbn-xsr By default, you must use `kbn-xsrf` for all API calls, except in the following scenarios: * The API endpoint uses the `GET` or `HEAD` operations - -* The path is whitelisted using the <> setting - -* XSRF protections are disabled using the `server.xsrf.disableProtection` setting +* The path is whitelisted using the <> setting +* XSRF protections are disabled using the <> setting `Content-Type: application/json`:: Applicable only when you send a payload in the API request. {kib} API requests and responses use JSON. Typically, if you include the `kbn-xsrf` header, you must also include the `Content-Type` header. diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 97fdcd3e13de9..01ba084b9e9e7 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -40,8 +40,8 @@ users interacting with APM APIs must have <> setting -* XSRF protections are disabled using the `server.xsrf.disableProtection` setting +* The path is whitelisted using the <> setting +* XSRF protections are disabled using the <> setting `Content-Type: application/json`:: Applicable only when you send a payload in the API request. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 01a9e96484965..359b0396dfcb9 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -571,7 +571,7 @@ all http requests to https over the port configured as `server.port`. | An array of supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`. *Default: TLSv1.1, TLSv1.2* -| `server.xsrf.whitelist:` +| [[settings-xsrf-whitelist]] `server.xsrf.whitelist:` | It is not recommended to disable protections for arbitrary API endpoints. Instead, supply the `kbn-xsrf` header. The `server.xsrf.whitelist` setting requires the following format: @@ -586,6 +586,9 @@ The `server.xsrf.whitelist` setting requires the following format: [cols="2*<"] |=== +| [[settings-xsrf-disableProtection]] `status.xsrf.disableProtection:` + | Setting this to `true` will completely disable Cross-site request forgery protection in Kibana. This is not recommended. *Default: `false`* + | `status.allowAnonymous:` | If authentication is enabled, setting this to `true` enables unauthenticated users to access the {kib} From 4e8dd3e2e224983d17ad3abde138b8edc7770f25 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 27 Aug 2020 10:52:18 -0600 Subject: [PATCH 23/39] do not advance beneathMbLayerId if bottomMbLayer could not be found for a layer (#76007) (#76087) Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../map/mb/sort_layers.test.ts | 19 +++++++++++++++++++ .../map/mb/sort_layers.ts | 6 ++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts index 273611e94ee40..e26a1e43509c8 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts +++ b/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts @@ -200,6 +200,25 @@ describe('sortLayer', () => { ]); }); + // Hidden map layers on map load may not add mbLayers to mbStyle. + test('Should sort with missing mblayers to expected order', () => { + // Notice there are no bravo mbLayers in initial style. + const initialMbStyle = { + version: 0, + layers: [ + { id: `${CHARLIE_LAYER_ID}_fill`, type: 'fill' } as MbLayer, + { id: `${ALPHA_LAYER_ID}_circle`, type: 'circle' } as MbLayer, + ], + }; + const mbMap = new MockMbMap(initialMbStyle); + syncLayerOrder((mbMap as unknown) as MbMap, spatialFilterLayer, mapLayers); + const sortedMbStyle = mbMap.getStyle(); + const sortedMbLayerIds = sortedMbStyle.layers!.map((mbLayer) => { + return mbLayer.id; + }); + expect(sortedMbLayerIds).toEqual(['charlie_fill', 'alpha_circle']); + }); + test('Should not call move layers when layers are in expected order', () => { const initialMbStyle = { version: 0, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts index 4752eeba2376a..0c970fe663557 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts +++ b/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts @@ -128,7 +128,8 @@ export function syncLayerOrder(mbMap: MbMap, spatialFiltersLayer: ILayer, layerL if (!isLayerInOrder(mbMap, mapLayer, LAYER_CLASS.LABEL, beneathMbLayerId)) { moveMapLayer(mbMap, mbLayers, mapLayer, LAYER_CLASS.LABEL, beneathMbLayerId); } - beneathMbLayerId = getBottomMbLayerId(mbLayers, mapLayer, LAYER_CLASS.LABEL); + const bottomMbLayerId = getBottomMbLayerId(mbLayers, mapLayer, LAYER_CLASS.LABEL); + if (bottomMbLayerId) beneathMbLayerId = bottomMbLayerId; }); // Sort map layers @@ -137,6 +138,7 @@ export function syncLayerOrder(mbMap: MbMap, spatialFiltersLayer: ILayer, layerL if (!isLayerInOrder(mbMap, mapLayer, layerClass, beneathMbLayerId)) { moveMapLayer(mbMap, mbLayers, mapLayer, layerClass, beneathMbLayerId); } - beneathMbLayerId = getBottomMbLayerId(mbLayers, mapLayer, layerClass); + const bottomMbLayerId = getBottomMbLayerId(mbLayers, mapLayer, layerClass); + if (bottomMbLayerId) beneathMbLayerId = bottomMbLayerId; }); } From 7249507f71956f4b39b475b92aad05b656f1695f Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:05:44 -0400 Subject: [PATCH 24/39] [Docs] Fix typo in docs for `server.xsrf.disableProtection` (#76102) (#76115) --- docs/setup/settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 359b0396dfcb9..d36b00f1a4615 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -586,7 +586,7 @@ The `server.xsrf.whitelist` setting requires the following format: [cols="2*<"] |=== -| [[settings-xsrf-disableProtection]] `status.xsrf.disableProtection:` +| [[settings-xsrf-disableProtection]] `server.xsrf.disableProtection:` | Setting this to `true` will completely disable Cross-site request forgery protection in Kibana. This is not recommended. *Default: `false`* | `status.allowAnonymous:` From 1046fbbf88167b82f66cfbcd5f4ac43b15a8b34d Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 27 Aug 2020 11:19:32 -0700 Subject: [PATCH 25/39] [DOCS] Add monitoring.ui.logs.index (#75830) (#76128) --- docs/settings/monitoring-settings.asciidoc | 43 ++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 5b8fa0725d96b..d538519eefcc4 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -5,12 +5,12 @@ Monitoring settings ++++ -By default, the Monitoring application is enabled, but data collection -is disabled. When you first start {kib} monitoring, you are prompted to -enable data collection. If you are using {stack-security-features}, you must be -signed in as a user with the `cluster:manage` privilege to enable -data collection. The built-in `superuser` role has this privilege and the -built-in `elastic` user has this role. +By default, *{stack-monitor-app}* is enabled, but data collection is disabled. +When you first start {kib} monitoring, you are prompted to enable data +collection. If you are using {stack-security-features}, you must be signed in as +a user with the `cluster:manage` privilege to enable data collection. The +built-in `superuser` role has this privilege and the built-in `elastic` user has +this role. You can adjust how monitoring data is collected from {kib} and displayed in {kib} by configuring settings in the @@ -49,7 +49,7 @@ For more information, see in {kib} to the {es} monitoring cluster and to verify licensing status on the {es} monitoring cluster. + + - Every other request performed by the Stack Monitoring UI to the monitoring {es} + Every other request performed by *{stack-monitor-app}* to the monitoring {es} cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + @@ -60,7 +60,7 @@ For more information, see in {kib} to the {es} monitoring cluster and to verify licensing status on the {es} monitoring cluster. + + - Every other request performed by the Stack Monitoring UI to the monitoring {es} + Every other request performed by *{stack-monitor-app}* to the monitoring {es} cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + @@ -83,7 +83,7 @@ These settings control how data is collected from {kib}. |=== | `monitoring.kibana.collection.enabled` | Set to `true` (default) to enable data collection from the {kib} NodeJS server - for {kib} Dashboards to be featured in the Monitoring. + for {kib} dashboards to be featured in *{stack-monitor-app}*. | `monitoring.kibana.collection.interval` | Specifies the number of milliseconds to wait in between data sampling on the @@ -96,16 +96,26 @@ These settings control how data is collected from {kib}. [[monitoring-ui-settings]] ==== Monitoring UI settings -These settings adjust how the {kib} Monitoring page displays monitoring data. +These settings adjust how *{stack-monitor-app}* displays monitoring data. However, the defaults work best in most circumstances. For more information about configuring {kib}, see -{kibana-ref}/settings.html[Setting Kibana Server Properties]. +{kibana-ref}/settings.html[Setting {kib} server properties]. [cols="2*<"] |=== | `monitoring.ui.elasticsearch.logFetchCount` - | Specifies the number of log entries to display in the Monitoring UI. Defaults to - `10`. The maximum value is `50`. + | Specifies the number of log entries to display in *{stack-monitor-app}*. + Defaults to `10`. The maximum value is `50`. + +| `monitoring.ui.enabled` + | Set to `false` to hide *{stack-monitor-app}*. The monitoring back-end + continues to run as an agent for sending {kib} stats to the monitoring + cluster. Defaults to `true`. + +| `monitoring.ui.logs.index` + | Specifies the name of the indices that are shown on the + <> page in *{stack-monitor-app}*. The default value + is `filebeat-*`. | `monitoring.ui.max_bucket_size` | Specifies the number of term buckets to return out of the overall terms list when @@ -120,18 +130,13 @@ about configuring {kib}, see `monitoring.ui.collection.interval` in `elasticsearch.yml`, use the same value in this setting. -| `monitoring.ui.enabled` - | Set to `false` to hide the Monitoring UI in {kib}. The monitoring back-end - continues to run as an agent for sending {kib} stats to the monitoring - cluster. Defaults to `true`. - |=== [float] [[monitoring-ui-cgroup-settings]] ===== Monitoring UI container settings -The Monitoring UI exposes the Cgroup statistics that we collect for you to make +*{stack-monitor-app}* exposes the Cgroup statistics that we collect for you to make better decisions about your container performance, rather than guessing based on the overall machine performance. If you are not running your applications in a container, then Cgroup statistics are not useful. From a8af78b355e9602206a9de4f8b7ee3bf374615fc Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 27 Aug 2020 20:15:25 +0100 Subject: [PATCH 26/39] fix(NA): keystore read path on serve (#75659) (#76124) Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- src/cli/serve/serve.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 972bcdba6b403..9572c8143d75a 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -155,7 +155,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { ); merge(extraCliOptions); - merge(readKeystore(get('path.data'))); + merge(readKeystore()); return rawConfig; } From 051252f2cb01b7da6a09a3f71be1a937b33a7cf1 Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Thu, 27 Aug 2020 14:17:04 -0800 Subject: [PATCH 27/39] [Detection Rules] Add 7.9.1 rules (#75939) (#76175) * increase lookback (`from`) and bump versions --- .../command_and_control_certutil_network_connection.json | 3 ++- .../credential_access_credential_dumping_msbuild.json | 3 ++- .../prepackaged_rules/credential_access_tcpdump_activity.json | 3 ++- ...on_adding_the_hidden_file_attribute_with_via_attribexe.json | 3 ++- ...efense_evasion_attempt_to_disable_iptables_or_firewall.json | 3 ++- .../defense_evasion_attempt_to_disable_syslog_service.json | 3 ++- ...evasion_base16_or_base32_encoding_or_decoding_activity.json | 3 ++- .../defense_evasion_base64_encoding_or_decoding_activity.json | 3 ++- .../defense_evasion_clearing_windows_event_logs.json | 3 ++- .../defense_evasion_delete_volume_usn_journal_with_fsutil.json | 3 ++- .../defense_evasion_deleting_backup_catalogs_with_wbadmin.json | 3 ++- .../defense_evasion_deletion_of_bash_command_line_history.json | 3 ++- .../defense_evasion_disable_selinux_attempt.json | 3 ++- ...ense_evasion_disable_windows_firewall_rules_with_netsh.json | 3 ++- ...efense_evasion_encoding_or_decoding_files_via_certutil.json | 3 ++- ...efense_evasion_execution_msbuild_started_by_office_app.json | 3 ++- .../defense_evasion_execution_msbuild_started_by_script.json | 3 ++- ...se_evasion_execution_msbuild_started_by_system_process.json | 3 ++- .../defense_evasion_execution_msbuild_started_renamed.json | 3 ++- ...fense_evasion_execution_msbuild_started_unusal_process.json | 3 ++- .../defense_evasion_file_deletion_via_shred.json | 3 ++- .../defense_evasion_file_mod_writable_dir.json | 3 ++- .../defense_evasion_hex_encoding_or_decoding_activity.json | 3 ++- .../prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json | 3 ++- .../defense_evasion_kernel_module_removal.json | 3 ++- ...defense_evasion_misc_lolbin_connecting_to_the_internet.json | 3 ++- .../defense_evasion_modification_of_boot_config.json | 3 ++- ...fense_evasion_volume_shadow_copy_deletion_via_vssadmin.json | 3 ++- .../defense_evasion_volume_shadow_copy_deletion_via_wmic.json | 3 ++- .../prepackaged_rules/discovery_kernel_module_enumeration.json | 3 ++- .../discovery_net_command_system_account.json | 3 ++- .../discovery_virtual_machine_fingerprinting.json | 3 ++- .../rules/prepackaged_rules/discovery_whoami_commmand.json | 3 ++- .../execution_command_prompt_connecting_to_the_internet.json | 3 ++- .../execution_command_shell_started_by_powershell.json | 3 ++- .../execution_command_shell_started_by_svchost.json | 3 ++- ...tml_help_executable_program_connecting_to_the_internet.json | 3 ++- .../prepackaged_rules/execution_local_service_commands.json | 3 ++- .../execution_msbuild_making_network_connections.json | 3 ++- .../execution_mshta_making_network_connections.json | 3 ++- .../rules/prepackaged_rules/execution_msxsl_network.json | 3 ++- .../rules/prepackaged_rules/execution_perl_tty_shell.json | 3 ++- .../execution_psexec_lateral_movement_command.json | 3 ++- .../rules/prepackaged_rules/execution_python_tty_shell.json | 3 ++- ...ion_register_server_program_connecting_to_the_internet.json | 3 ++- .../execution_script_executing_powershell.json | 3 ++- .../execution_suspicious_ms_office_child_process.json | 3 ++- .../execution_suspicious_ms_outlook_child_process.json | 3 ++- .../prepackaged_rules/execution_suspicious_pdf_reader.json | 3 ++- .../execution_unusual_network_connection_via_rundll32.json | 3 ++- .../execution_unusual_process_network_connection.json | 3 ++- .../prepackaged_rules/execution_via_net_com_assemblies.json | 3 ++- .../lateral_movement_direct_outbound_smb_connection.json | 3 ++- .../lateral_movement_telnet_network_activity_external.json | 3 ++- .../lateral_movement_telnet_network_activity_internal.json | 3 ++- .../rules/prepackaged_rules/linux_hping_activity.json | 3 ++- .../rules/prepackaged_rules/linux_iodine_activity.json | 3 ++- .../rules/prepackaged_rules/linux_mknod_activity.json | 3 ++- .../prepackaged_rules/linux_netcat_network_connection.json | 3 ++- .../rules/prepackaged_rules/linux_nmap_activity.json | 3 ++- .../rules/prepackaged_rules/linux_nping_activity.json | 3 ++- .../linux_process_started_in_temp_directory.json | 3 ++- .../rules/prepackaged_rules/linux_socat_activity.json | 3 ++- .../rules/prepackaged_rules/linux_strace_activity.json | 3 ++- .../persistence_adobe_hijack_persistence.json | 3 ++- .../prepackaged_rules/persistence_kernel_module_activity.json | 3 ++- .../persistence_local_scheduled_task_commands.json | 3 ++- .../persistence_shell_activity_by_web_server.json | 3 ++- .../persistence_system_shells_via_services.json | 3 ++- .../prepackaged_rules/persistence_user_account_creation.json | 3 ++- .../privilege_escalation_setgid_bit_set_via_chmod.json | 3 ++- .../privilege_escalation_setuid_bit_set_via_chmod.json | 3 ++- .../privilege_escalation_sudoers_file_mod.json | 3 ++- .../privilege_escalation_uac_bypass_event_viewer.json | 3 ++- .../privilege_escalation_unusual_parentchild_relationship.json | 3 ++- 75 files changed, 150 insertions(+), 75 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json index 25274928aa2b7..a8be0fe97524e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies certutil.exe making a network connection. Adversaries could abuse certutil.exe to download a certificate, or malware, from a remote URL.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json index 6be1f037f967e..f2032b5bef218 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_tcpdump_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_tcpdump_activity.json index d5b069f7b81e7..306a38f5d2a28 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_tcpdump_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_tcpdump_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some normal use of this command may originate from server or network administrators engaged in network troubleshooting." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json index b22b74ebc53bc..c80f24a21d958 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Adversaries can add the 'hidden' attribute to files to hide them from the user in an attempt to evade detection.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -51,5 +52,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_iptables_or_firewall.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_iptables_or_firewall.json index e2ba81da917b3..4d4f10bbaa599 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_iptables_or_firewall.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_iptables_or_firewall.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Adversaries may attempt to disable the iptables or firewall service in an attempt to affect how a host is allowed to receive or send network traffic.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json index 4f4a9aacd79aa..3c34b04a77a50 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Adversaries may attempt to disable the syslog service in an attempt to an attempt to disrupt event logging and evade detection by security controls.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json index 5bcc4a00ccd82..3cdfac92572b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Automated tools such as Jenkins may encode or decode files as part of their normal behavior. These events can be filtered by the process executable or username values." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base64_encoding_or_decoding_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base64_encoding_or_decoding_activity.json index a17fd6d2702dd..2d26d867b8718 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base64_encoding_or_decoding_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base64_encoding_or_decoding_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Automated tools such as Jenkins may encode or decode files as part of their normal behavior. These events can be filtered by the process executable or username values." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json index cf09bc512916f..60ce575148f4c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json index 0c82444dd9397..50213b9f1a42c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of the fsutil.exe to delete the volume USNJRNL. This technique is used by attackers to eliminate evidence of files created during post-exploitation activities.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json index c76c5f20fa88b..026735f413eab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of the wbadmin.exe to delete the backup catalog. Ransomware and other malware may do this to prevent system recovery.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json index b38ed94e132e1..85d8bdcb2582f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Adversaries may attempt to clear the bash command line history in an attempt to evade detection or forensic investigations.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json index 229a03de39600..d107c0b262091 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies potential attempts to disable Security-Enhanced Linux (SELinux), which is a Linux kernel security feature to support access control policies. Adversaries may disable security tools to avoid possible detection of their tools and activities.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json index 4800e87c180e2..6fbf9ca800f79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of the netsh.exe to disable or weaken the local firewall. Attackers will use this command line tool to disable the firewall during troubleshooting or to enable network mobility.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json index 075dd13d9819b..0d47aab2c64bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies the use of certutil.exe to encode or decode data. CertUtil is a native Windows component which is part of Certificate Services. CertUtil is often abused by attackers to encode or decode base64 data for stealthier command and control or exfiltration.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json index 133863f8e2148..df7fc85b63d4a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual. It is quite unusual for this program to be started by an Office application like Word or Excel." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -57,5 +58,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json index 85d348bb14be0..aa4674f75bcd0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json index 38482c0a70fc9..da7d91933bd2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json index 7db683caf2bb2..8e4f7366a7657 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json index 1c4666955dde0..4f353a6ff9e6f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json @@ -6,6 +6,7 @@ "false_positives": [ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual. If a build system triggers this rule it can be exempted by process, user or host name." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -42,5 +43,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json index c375ea7b19b37..5b02f63a1c7f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Malware or other files dropped or created on a system by an adversary may leave traces behind as to what was done within a network and how. Adversaries may remove these files over the course of an intrusion to keep their footprint low or remove them at the end as part of the post-intrusion cleanup process.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json index 22090e1a241e7..8ee2d4fda7bf8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json @@ -6,6 +6,7 @@ "false_positives": [ "Certain programs or applications may modify files or change ownership in writable directories. These can be exempted by username." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hex_encoding_or_decoding_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hex_encoding_or_decoding_activity.json index 00491937e9aae..f5345b2276e8a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hex_encoding_or_decoding_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hex_encoding_or_decoding_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Automated tools such as Jenkins may encode or decode files as part of their normal behavior. These events can be filtered by the process executable or username values." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json index 16a398011fc53..e66968a50709e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json @@ -6,6 +6,7 @@ "false_positives": [ "Certain tools may create hidden temporary files or directories upon installation or as part of their normal behavior. These events can be filtered by the process arguments, username, or process name values." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -55,5 +56,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json index 11781cb719599..ad751a1031437 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json @@ -6,6 +6,7 @@ "false_positives": [ "There is usually no reason to remove modules, but some buggy modules require it. These can be exempted by username. Note that some Linux distributions are not built to support the removal of modules at all." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -57,5 +58,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json index 7d931725fa6eb..5b5f69a0aef74 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Binaries signed with trusted digital certificates can execute on Windows systems protected by digital signature validation. Adversaries may use these binaries to 'live off the land' and execute malicious files that could bypass application allowlists and signature validation.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -51,5 +52,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json index 1bffe7a1cfc24..6025fc5ca6452 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of bcdedit.exe to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_vssadmin.json index f3cc5c2eec8a3..8a504281b03f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_vssadmin.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of vssadmin.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json index 334276142ca42..2ae938bb34104 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of wmic.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json index 0e4bea426c591..af9c4b5409964 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json @@ -6,6 +6,7 @@ "false_positives": [ "Security tools and device drivers may run these programs in order to enumerate kernel modules. Use of these programs by ordinary users is uncommon. These can be exempted by process name or username." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json index 6ac2bbf355961..f1a214b7cd436 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies the SYSTEM account using the Net utility. The Net utility is a component of the Windows operating system. It is used in command line operations for control of users, groups, services, and network connections.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json index e73aa5f4566a7..d913a92e2ee0e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json @@ -6,6 +6,7 @@ "false_positives": [ "Certain tools or automated software may enumerate hardware information. These tools can be exempted via user name or process arguments to eliminate potential noise." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_commmand.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_commmand.json index 0017186787139..a8b34362d9579 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_commmand.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_commmand.json @@ -6,6 +6,7 @@ "false_positives": [ "Security testing tools and frameworks may run this command. Some normal use of this command may originate from automation tools and frameworks." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json index 0ba6480fe42a1..46208f3753fa1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Administrators may use the command prompt for regular administrative tasks. It's important to baseline your environment for network connections being made from the command prompt to determine any abnormal use of this tool." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json index 2d3edb0f5f6cc..c619d8f764bc4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies a suspicious parent child process relationship with cmd.exe descending from PowerShell.exe.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -51,5 +52,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json index 3a4b4915f3c8b..140212e4148eb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies a suspicious parent child process relationship with cmd.exe descending from svchost.exe", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json index a2eb76b9831f0..963c6b2e53ed6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Compiled HTML files (.chm) are commonly distributed as part of the Microsoft HTML Help system. Adversaries may conceal malicious code in a CHM file and deliver it to a victim for execution. CHM content is loaded by the HTML Help executable program (hh.exe).", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -51,5 +52,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_local_service_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_local_service_commands.json index e43ab9de86ef7..7b20cefdc67f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_local_service_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_local_service_commands.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies use of sc.exe to create, modify, or start services on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msbuild_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msbuild_making_network_connections.json index 9d480259d49de..629efa90a71ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msbuild_making_network_connections.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msbuild_making_network_connections.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies MsBuild.exe making outbound network connections. This may indicate adversarial activity as MsBuild is often leveraged by adversaries to execute code and evade detection.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mshta_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mshta_making_network_connections.json index cdef5f16e5cd7..7af823070889f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mshta_making_network_connections.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mshta_making_network_connections.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies mshta.exe making a network connection. This may indicate adversarial activity as mshta.exe is often leveraged by adversaries to execute malicious scripts and evade detection.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msxsl_network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msxsl_network.json index d501bda08c3a5..1dc75575636fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msxsl_network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_msxsl_network.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies msxsl.exe making a network connection. This may indicate adversarial activity as msxsl.exe is often leveraged by adversaries to execute malicious scripts and evade detection.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json index e82b42869e44d..9b6ee099116f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies when a terminal (tty) is spawned via Perl. Attackers may upgrade a simple reverse shell to a fully interactive tty after obtaining initial access to a host.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json index e4c84fd3c3b83..f647d8d00e084 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json @@ -6,6 +6,7 @@ "false_positives": [ "PsExec is a dual-use tool that can be used for benign or malicious activity. It's important to baseline your environment to determine the amount of noise to expect from this tool." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json index 3aa9ac20bba9e..d9c26a9c26cc9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies when a terminal (tty) is spawned via Python. Attackers may upgrade a simple reverse shell to a fully interactive tty after obtaining initial access to a host.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json index 0a1ba97bd01ea..b3b6a2b0c7fab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Security testing may produce events like this. Activity of this kind performed by non-engineers and ordinary users is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -54,5 +55,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_executing_powershell.json index 7305247192f57..6d7f11f01fae0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_executing_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_executing_powershell.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies a PowerShell process launched by either cscript.exe or wscript.exe. Observing Windows scripting processes executing a PowerShell script, may be indicative of malicious activity.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_office_child_process.json index 7ff8eb9424d5f..005a0c38c8a8b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_office_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_office_child_process.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, Excel). These child processes are often launched during exploitation of Office applications or from documents with malicious macros.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_outlook_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_outlook_child_process.json index e923407765f8f..74e21c7d17479 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_outlook_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_ms_outlook_child_process.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies suspicious child processes of Microsoft Outlook. These child processes are often associated with spear phishing activity.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json index 24a744ce30832..adf1a76bfb901 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_network_connection_via_rundll32.json index 529f2199e46dc..1104159350655 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_network_connection_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_network_connection_via_rundll32.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies unusual instances of rundll32.exe making outbound network connections. This may indicate adversarial activity and may identify malicious DLLs.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_process_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_process_network_connection.json index 69a25b3b24bac..854ecc40d76ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_process_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_unusual_process_network_connection.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies network activity from unexpected system applications. This may indicate adversarial activity as these applications are often leveraged by adversaries to execute code and evade detection.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json index cae5d1b7e0f1f..d9dcbfe25a4c2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "RegSvcs.exe and RegAsm.exe are Windows command line utilities that are used to register .NET Component Object Model (COM) assemblies. Adversaries can use RegSvcs.exe and RegAsm.exe to proxy execution of code through a trusted Windows utility.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -51,5 +52,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json index 8a68b26abad20..e4014b22a6c09 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies unexpected processes making network connections over port 445. Windows File Sharing is typically implemented over Server Message Block (SMB), which communicates between hosts using port 445. When legitimate, these network connections are established by the kernel. Processes making 445/tcp connections may be port scanners, exploits, or suspicious user-level processes moving laterally.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json index 2ea75dbd758cb..e4804329c0f30 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json @@ -6,6 +6,7 @@ "false_positives": [ "Telnet can be used for both benign or malicious purposes. Telnet is included by default in some Linux distributions, so its presence is not inherently suspicious. The use of Telnet to manage devices remotely has declined in recent years in favor of more secure protocols such as SSH. Telnet usage by non-automated tools or frameworks may be suspicious." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json index 4379759608aba..30312987d166c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json @@ -6,6 +6,7 @@ "false_positives": [ "Telnet can be used for both benign or malicious purposes. Telnet is included by default in some Linux distributions, so its presence is not inherently suspicious. The use of Telnet to manage devices remotely has declined in recent years in favor of more secure protocols such as SSH. Telnet usage by non-automated tools or frameworks may be suspicious." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json index 24104439cd0ec..3a5c4d9e69d49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Normal use of hping is uncommon apart from security testing and research. Use by non-security engineers is very uncommon." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json index 73bf20a5a175e..63c82c5662df6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Normal use of Iodine is uncommon apart from security testing and research. Use by non-security engineers is very uncommon." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json index 1895caf4dea81..37d5468c773bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Mknod is a Linux system program. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools, and frameworks. Usage by web servers is more likely to be suspicious." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json index ac46bcbdbc083..bce10f640691b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json @@ -6,6 +6,7 @@ "false_positives": [ "Netcat is a dual-use tool that can be used for benign or malicious activity. Netcat is included in some Linux distributions so its presence is not necessarily suspicious. Some normal use of this program, while uncommon, may originate from scripts, automation tools, and frameworks." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -27,5 +28,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json index 2825dc28ad18f..5d9e338425bda 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Security testing tools and frameworks may run `Nmap` in the course of security auditing. Some normal use of this command may originate from security engineers and network or server administrators. Use of nmap by ordinary users is uncommon." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json index 234a09e9607b9..bd019c9a80c4c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some normal use of this command may originate from security engineers and network or server administrators, but this is usually not routine or unannounced. Use of `Nping` by non-engineers or ordinary users is uncommon." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index 759622804444e..f0bbc892d7d9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -6,6 +6,7 @@ "false_positives": [ "Build systems, like Jenkins, may start processes in the `/tmp` directory. These can be exempted by name or by username." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -22,5 +23,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json index cd38aff3f2164..fac03d31b57bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Socat is a dual-use tool that can be used for benign or malicious activity. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools, and frameworks. Usage by web servers is more likely to be suspicious." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json index 7fcb9f915c560..c1b782d612ccb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Strace is a dual-use tool that can be used for benign or malicious activity. Some normal use of this command may originate from developers or SREs engaged in debugging or system call tracing." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -25,5 +26,5 @@ "Linux" ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json index 3392a1bff23b8..a4c62b98fb060 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Detects writing executable files that will be automatically launched by Adobe on launch.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kernel_module_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kernel_module_activity.json index e76379d171bf7..e3dedeef07eb5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kernel_module_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kernel_module_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Security tools and device drivers may run these programs in order to load legitimate kernel modules. Use of these programs by ordinary users is uncommon." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -42,5 +43,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json index b9e7f941ee5df..8b81789f6aa8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json @@ -6,6 +6,7 @@ "false_positives": [ "Legitimate scheduled tasks may be created during installation of new software." ], + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -39,5 +40,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json index 0cf6fcdb3875a..2aaf0012acabf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json @@ -6,6 +6,7 @@ "false_positives": [ "Network monitoring or management products may have a web server component that runs shell commands as part of normal behavior." ], + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -42,5 +43,5 @@ } ], "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json index 59715dae441f4..32d78480325e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Windows services typically run as SYSTEM and can be used as a privilege escalation opportunity. Malware or penetration testers may run a shell as a service to gain SYSTEM permissions.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index 7465751d5cd49..3f2e00f0976de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies attempts to create new local users. This is sometimes done by attackers to increase access to a system or domain.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json index 9550eea6ca6aa..bb0856c0452d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "An adversary may add the setgid bit to a file or directory in order to run a file with the privileges of the owning group. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setgid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -52,5 +53,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json index 343426953add6..4cf60d2c9d0de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "An adversary may add the setuid bit to a file or directory in order to run a file with the privileges of the owning user. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setuid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -52,5 +53,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json index 44b50c74bafe6..73a804fcbda8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "A sudoers file specifies the commands that users or groups can run and from which terminals. Adversaries can take advantage of these configurations to execute commands as other users or spawn processes with higher privileges.", + "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json index 50692dae3856f..740ff47e5abe5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies User Account Control (UAC) bypass via eventvwr.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json index 8f938c0ceee6d..c6c5cbce2c095 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies Windows programs run from unexpected parent processes. This could indicate masquerading or other strange activity on a system.", + "from": "now-9m", "index": [ "winlogbeat-*", "logs-endpoint.events.*" @@ -36,5 +37,5 @@ } ], "type": "query", - "version": 3 + "version": 4 } From ce76ca3c284d6988de544d7993b99604685d833c Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 27 Aug 2020 19:07:04 -0400 Subject: [PATCH 28/39] [7.9] [Security Solution][Exceptions] - Improve UX for missing exception list associated with rule (#76012) (#76052) * [Security Solution][Exceptions] - Improve UX for missing exception list associated with rule (#76012) ## Summary **Current behavior:** - **Scenario 1:** User is in the exceptions viewer flow, they select to edit an exception item, but the list the item is associated with has since been deleted (let's say by another user) - a user is able to open modal to edit exception item and on save, an error toaster shows but no information is given to the user to indicate the issue. - **Scenario 2:** User exports rules from space 'X' and imports into space 'Y'. The exception lists associated with their newly imported rules do not exist in space 'Y' - a user goes to add an exception item and gets a modal with an error, unable to add any exceptions. - **Workaround:** current workaround exists only via API - user would need to remove the exception list from their rule via API **New behavior:** - **Scenario 1:** User is still able to oped edit modal, but on save they see an error explaining that the associated exception list does not exist and prompts them to remove the exception list --> now they're able to add exceptions to their rule - **Scenario 2:** User navigates to exceptions after importing their rule, tries to add exception, modal pops up with error informing them that they need to remove association to missing exception list, button prompts them to do so --> now can continue adding exceptions to rule --- .../exceptions/add_exception_modal/index.tsx | 89 ++++++--- .../exceptions/edit_exception_modal/index.tsx | 90 +++++++--- .../exceptions/error_callout.test.tsx | 160 +++++++++++++++++ .../components/exceptions/error_callout.tsx | 169 ++++++++++++++++++ .../components/exceptions/translations.ts | 49 +++++ .../exceptions/use_add_exception.test.tsx | 44 +++++ .../exceptions/use_add_exception.tsx | 8 +- ...tch_or_create_rule_exception_list.test.tsx | 2 +- ...se_fetch_or_create_rule_exception_list.tsx | 8 +- .../components/exceptions/viewer/index.tsx | 2 + .../use_dissasociate_exception_list.test.tsx | 52 ++++++ .../rules/use_dissasociate_exception_list.tsx | 80 +++++++++ 12 files changed, 701 insertions(+), 52 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 7526c52d16fde..8fb1489a3b66f 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -18,7 +18,6 @@ import { EuiCheckbox, EuiSpacer, EuiFormRow, - EuiCallOut, EuiText, } from '@elastic/eui'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; @@ -28,6 +27,7 @@ import { ExceptionListType, } from '../../../../../public/lists_plugin_deps'; import * as i18n from './translations'; +import * as sharedI18n from '../translations'; import { TimelineNonEcsData, Ecs } from '../../../../graphql/types'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import { useKibana } from '../../../lib/kibana'; @@ -35,6 +35,7 @@ import { ExceptionBuilderComponent } from '../builder'; import { Loader } from '../../loader'; import { useAddOrUpdateException } from '../use_add_exception'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { AddExceptionComments } from '../add_exception_comments'; import { @@ -46,6 +47,7 @@ import { entryHasNonEcsType, getMappedNonEcsValue, } from '../helpers'; +import { ErrorInfo, ErrorCallout } from '../error_callout'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; export interface AddExceptionModalBaseProps { @@ -107,13 +109,14 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }: AddExceptionModalProps) { const { http } = useKibana().services; const [comment, setComment] = useState(''); + const { rule: maybeRule } = useRuleAsync(ruleId); const [shouldCloseAlert, setShouldCloseAlert] = useState(false); const [shouldBulkCloseAlert, setShouldBulkCloseAlert] = useState(false); const [shouldDisableBulkClose, setShouldDisableBulkClose] = useState(false); const [exceptionItemsToAdd, setExceptionItemsToAdd] = useState< Array >([]); - const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); + const [fetchOrCreateListError, setFetchOrCreateListError] = useState(null); const { addError, addSuccess } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [ @@ -164,17 +167,41 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [onRuleChange] ); - const onFetchOrCreateExceptionListError = useCallback( - (error: Error) => { - setFetchOrCreateListError(true); + + const handleDissasociationSuccess = useCallback( + (id: string): void => { + handleRuleChange(true); + addSuccess(sharedI18n.DISSASOCIATE_LIST_SUCCESS(id)); + onCancel(); + }, + [handleRuleChange, addSuccess, onCancel] + ); + + const handleDissasociationError = useCallback( + (error: Error): void => { + addError(error, { title: sharedI18n.DISSASOCIATE_EXCEPTION_LIST_ERROR }); + onCancel(); + }, + [addError, onCancel] + ); + + const handleFetchOrCreateExceptionListError = useCallback( + (error: Error, statusCode: number | null, message: string | null) => { + setFetchOrCreateListError({ + reason: error.message, + code: statusCode, + details: message, + listListId: null, + }); }, [setFetchOrCreateListError] ); + const [isLoadingExceptionList, ruleExceptionList] = useFetchOrCreateRuleExceptionList({ http, ruleId, exceptionListType, - onError: onFetchOrCreateExceptionListError, + onError: handleFetchOrCreateExceptionListError, onSuccess: handleRuleChange, }); @@ -279,7 +306,9 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ]); const isSubmitButtonDisabled = useMemo( - () => fetchOrCreateListError || exceptionItemsToAdd.every((item) => item.entries.length === 0), + () => + fetchOrCreateListError != null || + exceptionItemsToAdd.every((item) => item.entries.length === 0), [fetchOrCreateListError, exceptionItemsToAdd] ); @@ -295,19 +324,27 @@ export const AddExceptionModal = memo(function AddExceptionModal({ - {fetchOrCreateListError === true && ( - -

{i18n.ADD_EXCEPTION_FETCH_ERROR}

-
+ {fetchOrCreateListError != null && ( + + + )} - {fetchOrCreateListError === false && + {fetchOrCreateListError == null && (isLoadingExceptionList || isIndexPatternLoading || isSignalIndexLoading || isSignalIndexPatternLoading) && ( )} - {fetchOrCreateListError === false && + {fetchOrCreateListError == null && !isSignalIndexLoading && !isSignalIndexPatternLoading && !isLoadingExceptionList && @@ -375,19 +412,21 @@ export const AddExceptionModal = memo(function AddExceptionModal({ )} + {fetchOrCreateListError == null && ( + + {i18n.CANCEL} - - {i18n.CANCEL} - - - {i18n.ADD_EXCEPTION} - - + + {i18n.ADD_EXCEPTION} + + + )} ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index e1352ac38dc49..dbc7574095ac4 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -23,12 +23,14 @@ import { } from '@elastic/eui'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; import { ExceptionListItemSchema, CreateExceptionListItemSchema, ExceptionListType, } from '../../../../../public/lists_plugin_deps'; import * as i18n from './translations'; +import * as sharedI18n from '../translations'; import { useKibana } from '../../../lib/kibana'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import { ExceptionBuilderComponent } from '../builder'; @@ -43,14 +45,17 @@ import { lowercaseHashValues, } from '../helpers'; import { Loader } from '../../loader'; +import { ErrorInfo, ErrorCallout } from '../error_callout'; interface EditExceptionModalProps { ruleName: string; + ruleId: string; ruleIndices: string[]; exceptionItem: ExceptionListItemSchema; exceptionListType: ExceptionListType; onCancel: () => void; onConfirm: () => void; + onRuleChange?: () => void; } const Modal = styled(EuiModal)` @@ -83,14 +88,18 @@ const ModalBodySection = styled.section` export const EditExceptionModal = memo(function EditExceptionModal({ ruleName, + ruleId, ruleIndices, exceptionItem, exceptionListType, onCancel, onConfirm, + onRuleChange, }: EditExceptionModalProps) { const { http } = useKibana().services; const [comment, setComment] = useState(''); + const { rule: maybeRule } = useRuleAsync(ruleId); + const [updateError, setUpdateError] = useState(null); const [hasVersionConflict, setHasVersionConflict] = useState(false); const [shouldBulkCloseAlert, setShouldBulkCloseAlert] = useState(false); const [shouldDisableBulkClose, setShouldDisableBulkClose] = useState(false); @@ -108,18 +117,44 @@ export const EditExceptionModal = memo(function EditExceptionModal({ 'rules' ); - const onError = useCallback( - (error) => { + const handleExceptionUpdateError = useCallback( + (error: Error, statusCode: number | null, message: string | null) => { if (error.message.includes('Conflict')) { setHasVersionConflict(true); } else { - addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); - onCancel(); + setUpdateError({ + reason: error.message, + code: statusCode, + details: message, + listListId: exceptionItem.list_id, + }); } }, + [setUpdateError, setHasVersionConflict, exceptionItem.list_id] + ); + + const handleDissasociationSuccess = useCallback( + (id: string): void => { + addSuccess(sharedI18n.DISSASOCIATE_LIST_SUCCESS(id)); + + if (onRuleChange) { + onRuleChange(); + } + + onCancel(); + }, + [addSuccess, onCancel, onRuleChange] + ); + + const handleDissasociationError = useCallback( + (error: Error): void => { + addError(error, { title: sharedI18n.DISSASOCIATE_EXCEPTION_LIST_ERROR }); + onCancel(); + }, [addError, onCancel] ); - const onSuccess = useCallback(() => { + + const handleExceptionUpdateSuccess = useCallback((): void => { addSuccess(i18n.EDIT_EXCEPTION_SUCCESS); onConfirm(); }, [addSuccess, onConfirm]); @@ -127,8 +162,8 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException( { http, - onSuccess, - onError, + onSuccess: handleExceptionUpdateSuccess, + onError: handleExceptionUpdateError, } ); @@ -222,11 +257,9 @@ export const EditExceptionModal = memo(function EditExceptionModal({ {ruleName} - {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && ( )} - {!isSignalIndexLoading && !addExceptionIsLoading && !isIndexPatternLoading && ( <> @@ -279,7 +312,18 @@ export const EditExceptionModal = memo(function EditExceptionModal({ )} - + {updateError != null && ( + + + + )} {hasVersionConflict && ( @@ -287,19 +331,21 @@ export const EditExceptionModal = memo(function EditExceptionModal({ )} + {updateError == null && ( + + {i18n.CANCEL} - - {i18n.CANCEL} - - - {i18n.EDIT_EXCEPTION_SAVE_BUTTON} - - + + {i18n.EDIT_EXCEPTION_SAVE_BUTTON} + + + )} ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx new file mode 100644 index 0000000000000..9c86c502a7648 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { getListMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; +import { useDissasociateExceptionList } from '../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list'; +import { ErrorCallout } from './error_callout'; +import { savedRuleMock } from '../../../detections/containers/detection_engine/rules/mock'; + +jest.mock('../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list'); + +const mockKibanaHttpService = coreMock.createStart().http; + +describe('ErrorCallout', () => { + const mockDissasociate = jest.fn(); + + beforeEach(() => { + (useDissasociateExceptionList as jest.Mock).mockReturnValue([false, mockDissasociate]); + }); + + it('it renders error details', () => { + const wrapper = mountWithIntl( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect( + wrapper.find('[data-test-subj="errorCalloutContainer"] .euiCallOutHeader__title').text() + ).toEqual('Error: error reason (500)'); + expect(wrapper.find('[data-test-subj="errorCalloutMessage"]').at(0).text()).toEqual( + 'Error fetching exception list' + ); + }); + + it('it invokes "onCancel" when cancel button clicked', () => { + const mockOnCancel = jest.fn(); + const wrapper = mountWithIntl( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + wrapper.find('[data-test-subj="errorCalloutCancelButton"]').at(0).simulate('click'); + + expect(mockOnCancel).toHaveBeenCalled(); + }); + + it('it does not render status code if not available', () => { + const wrapper = mountWithIntl( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect( + wrapper.find('[data-test-subj="errorCalloutContainer"] .euiCallOutHeader__title').text() + ).toEqual('Error: not found'); + expect(wrapper.find('[data-test-subj="errorCalloutMessage"]').at(0).text()).toEqual( + 'Error fetching exception list' + ); + expect(wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').exists()).toBeFalsy(); + }); + + it('it renders specific missing exceptions list error', () => { + const wrapper = mountWithIntl( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect( + wrapper.find('[data-test-subj="errorCalloutContainer"] .euiCallOutHeader__title').text() + ).toEqual('Error: not found (404)'); + expect(wrapper.find('[data-test-subj="errorCalloutMessage"]').at(0).text()).toEqual( + 'The associated exception list (some_uuid) no longer exists. Please remove the missing exception list to add additional exceptions to the detection rule.' + ); + expect(wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').exists()).toBeTruthy(); + }); + + it('it dissasociates list from rule when remove exception list clicked ', () => { + const wrapper = mountWithIntl( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').at(0).simulate('click'); + + expect(mockDissasociate).toHaveBeenCalledWith([]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.tsx new file mode 100644 index 0000000000000..a2419ef16df3a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useEffect, useState, useCallback } from 'react'; +import { + EuiButtonEmpty, + EuiAccordion, + EuiCodeBlock, + EuiButton, + EuiCallOut, + EuiText, + EuiSpacer, +} from '@elastic/eui'; + +import { HttpSetup } from '../../../../../../../src/core/public'; +import { List } from '../../../../common/detection_engine/schemas/types/lists'; +import { Rule } from '../../../detections/containers/detection_engine/rules/types'; +import * as i18n from './translations'; +import { useDissasociateExceptionList } from '../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list'; + +export interface ErrorInfo { + reason: string | null; + code: number | null; + details: string | null; + listListId: string | null; +} + +export interface ErrorCalloutProps { + http: HttpSetup; + rule: Rule | null; + errorInfo: ErrorInfo; + onCancel: () => void; + onSuccess: (listId: string) => void; + onError: (arg: Error) => void; +} + +const ErrorCalloutComponent = ({ + http, + rule, + errorInfo, + onCancel, + onError, + onSuccess, +}: ErrorCalloutProps): JSX.Element => { + const [listToDelete, setListToDelete] = useState(null); + const [errorTitle, setErrorTitle] = useState(''); + const [errorMessage, setErrorMessage] = useState(i18n.ADD_EXCEPTION_FETCH_ERROR); + + const handleOnSuccess = useCallback((): void => { + onSuccess(listToDelete != null ? listToDelete.id : ''); + }, [onSuccess, listToDelete]); + + const [isDissasociatingList, handleDissasociateExceptionList] = useDissasociateExceptionList({ + http, + ruleRuleId: rule != null ? rule.rule_id : '', + onSuccess: handleOnSuccess, + onError, + }); + + const canDisplay404Actions = useMemo( + (): boolean => + errorInfo.code === 404 && + rule != null && + listToDelete != null && + handleDissasociateExceptionList != null, + [errorInfo.code, listToDelete, handleDissasociateExceptionList, rule] + ); + + useEffect((): void => { + // Yes, it's redundant, unfortunately typescript wasn't picking up + // that `listToDelete` is checked in canDisplay404Actions + if (canDisplay404Actions && listToDelete != null) { + setErrorMessage(i18n.ADD_EXCEPTION_FETCH_404_ERROR(listToDelete.id)); + } + + setErrorTitle(`${errorInfo.reason}${errorInfo.code != null ? ` (${errorInfo.code})` : ''}`); + }, [errorInfo.reason, errorInfo.code, listToDelete, canDisplay404Actions]); + + const handleDissasociateList = useCallback((): void => { + // Yes, it's redundant, unfortunately typescript wasn't picking up + // that `handleDissasociateExceptionList` and `list` are checked in + // canDisplay404Actions + if ( + canDisplay404Actions && + rule != null && + listToDelete != null && + handleDissasociateExceptionList != null + ) { + const exceptionLists = (rule.exceptions_list ?? []).filter( + ({ id }) => id !== listToDelete.id + ); + + handleDissasociateExceptionList(exceptionLists); + } + }, [handleDissasociateExceptionList, listToDelete, canDisplay404Actions, rule]); + + useEffect((): void => { + if (errorInfo.code === 404 && rule != null && rule.exceptions_list != null) { + const [listFound] = rule.exceptions_list.filter( + ({ id, list_id: listId }) => + (errorInfo.details != null && errorInfo.details.includes(id)) || + errorInfo.listListId === listId + ); + setListToDelete(listFound); + } + }, [rule, errorInfo.details, errorInfo.code, errorInfo.listListId]); + + return ( + + +

{errorMessage}

+
+ + {listToDelete != null && ( + +

{i18n.MODAL_ERROR_ACCORDION_TEXT}

+ + } + > + + {JSON.stringify(listToDelete)} + +
+ )} + + + {i18n.CANCEL} + + {canDisplay404Actions && ( + + {i18n.CLEAR_EXCEPTIONS_LABEL} + + )} +
+ ); +}; + +ErrorCalloutComponent.displayName = 'ErrorCalloutComponent'; + +export const ErrorCallout = React.memo(ErrorCalloutComponent); + +ErrorCallout.displayName = 'ErrorCallout'; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts index 13e9d0df549f8..484a3d593026e 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts @@ -190,3 +190,52 @@ export const TOTAL_ITEMS_FETCH_ERROR = i18n.translate( defaultMessage: 'Error getting exception item totals', } ); + +export const CLEAR_EXCEPTIONS_LABEL = i18n.translate( + 'xpack.securitySolution.exceptions.clearExceptionsLabel', + { + defaultMessage: 'Remove Exception List', + } +); + +export const ADD_EXCEPTION_FETCH_404_ERROR = (listId: string) => + i18n.translate('xpack.securitySolution.exceptions.fetch404Error', { + values: { listId }, + defaultMessage: + 'The associated exception list ({listId}) no longer exists. Please remove the missing exception list to add additional exceptions to the detection rule.', + }); + +export const ADD_EXCEPTION_FETCH_ERROR = i18n.translate( + 'xpack.securitySolution.exceptions.fetchError', + { + defaultMessage: 'Error fetching exception list', + } +); + +export const ERROR = i18n.translate('xpack.securitySolution.exceptions.errorLabel', { + defaultMessage: 'Error', +}); + +export const CANCEL = i18n.translate('xpack.securitySolution.exceptions.cancelLabel', { + defaultMessage: 'Cancel', +}); + +export const MODAL_ERROR_ACCORDION_TEXT = i18n.translate( + 'xpack.securitySolution.exceptions.modalErrorAccordionText', + { + defaultMessage: 'Show rule reference information:', + } +); + +export const DISSASOCIATE_LIST_SUCCESS = (id: string) => + i18n.translate('xpack.securitySolution.exceptions.dissasociateListSuccessText', { + values: { id }, + defaultMessage: 'Exception list ({id}) has successfully been removed', + }); + +export const DISSASOCIATE_EXCEPTION_LIST_ERROR = i18n.translate( + 'xpack.securitySolution.exceptions.dissasociateExceptionListError', + { + defaultMessage: 'Failed to remove exception list', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx index cb1a80abedb27..8a7cba9e0e863 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx @@ -148,6 +148,50 @@ describe('useAddOrUpdateException', () => { }); }); + it('invokes "onError" if call to add exception item fails', async () => { + const mockError = new Error('error adding item'); + + addExceptionListItem = jest + .spyOn(listsApi, 'addExceptionListItem') + .mockRejectedValue(mockError); + + await act(async () => { + const { rerender, result, waitForNextUpdate } = render(); + const addOrUpdateItems = await waitForAddOrUpdateFunc({ + rerender, + result, + waitForNextUpdate, + }); + if (addOrUpdateItems) { + addOrUpdateItems(...addOrUpdateItemsArgs); + } + await waitForNextUpdate(); + expect(onError).toHaveBeenCalledWith(mockError, null, null); + }); + }); + + it('invokes "onError" if call to update exception item fails', async () => { + const mockError = new Error('error updating item'); + + updateExceptionListItem = jest + .spyOn(listsApi, 'updateExceptionListItem') + .mockRejectedValue(mockError); + + await act(async () => { + const { rerender, result, waitForNextUpdate } = render(); + const addOrUpdateItems = await waitForAddOrUpdateFunc({ + rerender, + result, + waitForNextUpdate, + }); + if (addOrUpdateItems) { + addOrUpdateItems(...addOrUpdateItemsArgs); + } + await waitForNextUpdate(); + expect(onError).toHaveBeenCalledWith(mockError, null, null); + }); + }); + describe('when alertIdToClose is not passed in', () => { it('should not update the alert status', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx index 9d45a411b5130..be289b0e85e66 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx @@ -42,7 +42,7 @@ export type ReturnUseAddOrUpdateException = [ export interface UseAddOrUpdateExceptionProps { http: HttpStart; - onError: (arg: Error) => void; + onError: (arg: Error, code: number | null, message: string | null) => void; onSuccess: () => void; } @@ -157,7 +157,11 @@ export const useAddOrUpdateException = ({ } catch (error) { if (isSubscribed) { setIsLoading(false); - onError(error); + if (error.body != null) { + onError(error, error.body.status_code, error.body.message); + } else { + onError(error, null, null); + } } } }; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.test.tsx index 6dbf5922e0a97..72b536c0e68e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.test.tsx @@ -379,7 +379,7 @@ describe('useFetchOrCreateRuleExceptionList', () => { await waitForNextUpdate(); await waitForNextUpdate(); expect(onError).toHaveBeenCalledTimes(1); - expect(onError).toHaveBeenCalledWith(error); + expect(onError).toHaveBeenCalledWith(error, null, null); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.tsx index 0d367e03a799f..944631d4e9fb5 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_fetch_or_create_rule_exception_list.tsx @@ -30,7 +30,7 @@ export interface UseFetchOrCreateRuleExceptionListProps { http: HttpStart; ruleId: Rule['id']; exceptionListType: ExceptionListSchema['type']; - onError: (arg: Error) => void; + onError: (arg: Error, code: number | null, message: string | null) => void; onSuccess?: (ruleWasChanged: boolean) => void; } @@ -179,7 +179,11 @@ export const useFetchOrCreateRuleExceptionList = ({ if (isSubscribed) { setIsLoading(false); setExceptionList(null); - onError(error); + if (error.body != null) { + onError(error, error.body.status_code, error.body.message); + } else { + onError(error, null, null); + } } } } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index 7482068454a97..c97895cdfe236 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -322,11 +322,13 @@ const ExceptionsViewerComponent = ({ exceptionListTypeToEdit != null && ( )} diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx new file mode 100644 index 0000000000000..6721d89f2799b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; + +import * as api from './api'; +import { ruleMock } from './mock'; +import { + ReturnUseDissasociateExceptionList, + UseDissasociateExceptionListProps, + useDissasociateExceptionList, +} from './use_dissasociate_exception_list'; + +const mockKibanaHttpService = coreMock.createStart().http; + +describe('useDissasociateExceptionList', () => { + const onError = jest.fn(); + const onSuccess = jest.fn(); + + beforeEach(() => { + jest.spyOn(api, 'patchRule').mockResolvedValue(ruleMock); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + UseDissasociateExceptionListProps, + ReturnUseDissasociateExceptionList + >(() => + useDissasociateExceptionList({ + http: mockKibanaHttpService, + ruleRuleId: 'rule_id', + onError, + onSuccess, + }) + ); + + await waitForNextUpdate(); + + expect(result.current).toEqual([false, null]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx new file mode 100644 index 0000000000000..dffba3e6e0436 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useRef } from 'react'; + +import { HttpStart } from '../../../../../../../../src/core/public'; +import { List } from '../../../../../common/detection_engine/schemas/types/lists'; +import { patchRule } from './api'; + +type Func = (lists: List[]) => void; +export type ReturnUseDissasociateExceptionList = [boolean, Func | null]; + +export interface UseDissasociateExceptionListProps { + http: HttpStart; + ruleRuleId: string; + onError: (arg: Error) => void; + onSuccess: () => void; +} + +/** + * Hook for removing an exception list reference from a rule + * + * @param http Kibana http service + * @param ruleRuleId a rule_id (NOT id) + * @param onError error callback + * @param onSuccess success callback + * + */ +export const useDissasociateExceptionList = ({ + http, + ruleRuleId, + onError, + onSuccess, +}: UseDissasociateExceptionListProps): ReturnUseDissasociateExceptionList => { + const [isLoading, setLoading] = useState(false); + const dissasociateList = useRef(null); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const dissasociateListFromRule = (id: string) => async ( + exceptionLists: List[] + ): Promise => { + try { + if (isSubscribed) { + setLoading(true); + + await patchRule({ + ruleProperties: { + rule_id: id, + exceptions_list: exceptionLists, + }, + signal: abortCtrl.signal, + }); + + onSuccess(); + setLoading(false); + } + } catch (err) { + if (isSubscribed) { + setLoading(false); + onError(err); + } + } + }; + + dissasociateList.current = dissasociateListFromRule(ruleRuleId); + + return (): void => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [http, ruleRuleId, onError, onSuccess]); + + return [isLoading, dissasociateList.current]; +}; From 221f5de12cecfce48d1e4239921774b4e1b62104 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 27 Aug 2020 18:35:07 -0500 Subject: [PATCH 29/39] [7.9] [APM] Fix query bar Japanese translation (#68037) (#76204) The order of the `select` statement items were reversed in the translation for `xpack.apm.kueryBar.placeholder`, causing an error in the KueryBar component which caused the page to fail to load. Fix the translation, which fixes the error. Fixes #67082. # Conflicts: # x-pack/plugins/translations/translations/ja-JP.json --- x-pack/plugins/translations/translations/ja-JP.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9f36f94e47754..1eadacfd873a8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4889,6 +4889,7 @@ "xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非ヒープ領域の平均", "xpack.apm.jvmsTable.threadCountColumnLabel": "最大スレッド数", "xpack.apm.kueryBar.disabledPlaceholder": "ここでは検索は利用できません", + "xpack.apm.kueryBar.placeholder": "検索 {event, select,\n transaction {トランザクション}\n metric {メトリック}\n error {エラー}\n other {その他}\n } (E.g. {queryExample})", "xpack.apm.license.betaBadge": "ベータ", "xpack.apm.license.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", "xpack.apm.license.button": "トライアルを開始", From ab4bc2d14cab4234f407a52f348cde81468b7b01 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 27 Aug 2020 16:44:32 -0700 Subject: [PATCH 30/39] [7.9] [docs/getting-started] link to yarn v1 specifically (#76169) (#76201) Co-authored-by: spalger Co-authored-by: spalger --- docs/developer/getting-started/index.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/getting-started/index.asciidoc b/docs/developer/getting-started/index.asciidoc index 47c4a52daf303..4ca41a6f0dee4 100644 --- a/docs/developer/getting-started/index.asciidoc +++ b/docs/developer/getting-started/index.asciidoc @@ -30,7 +30,7 @@ you can switch to the correct version when using nvm by running: nvm use ---- -Install the latest version of https://yarnpkg.com[yarn]. +Install the latest version of https://classic.yarnpkg.com/en/docs/install[yarn v1]. Bootstrap {kib} and install all the dependencies: From 33e9f90e8e617ac032cf2a02a5a1ebc4fe04e8fc Mon Sep 17 00:00:00 2001 From: Chris Cressman Date: Thu, 27 Aug 2020 21:09:39 -0400 Subject: [PATCH 31/39] Fixes App Search documentation links (#76184) Two links to App Search docs are pointing to outdated versions. Update the URLs. --- .../app_search/components/setup_guide/setup_guide.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx index df278bf938a69..c1986c028d3fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx @@ -20,8 +20,8 @@ export const SetupGuide: React.FC = () => ( defaultMessage: 'App Search', })} productEuiIcon="logoAppSearch" - standardAuthLink="https://swiftype.com/documentation/app-search/self-managed/security#standard" - elasticsearchNativeAuthLink="https://swiftype.com/documentation/app-search/self-managed/security#elasticsearch-native-realm" + standardAuthLink="https://www.elastic.co/guide/en/app-search/current/security-and-users.html#app-search-self-managed-security-and-user-management-standard" + elasticsearchNativeAuthLink="https://www.elastic.co/guide/en/app-search/current/security-and-users.html#app-search-self-managed-security-and-user-management-elasticsearch-native-realm" > From 5d804e58f81a75233ea902ba41a81b1a364a93cb Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 27 Aug 2020 23:03:00 -0400 Subject: [PATCH 32/39] [Security Solution][Exceptions] - Fix bug of alerts not updating after closure from exceptions modal (#76145) (#76217) ## Summary Fixes bug found in 7.9. **Current behavior:** - Go to alerts page - Select action to add exception item - Select checkbox to close all alerts matching exception - Click `Add exception` - Note that the alerts table does not update until after you refresh or toggle between `Closed` and `Open` **New behavior:** - Go to alerts page - Select action to add exception item - Select checkbox to close all alerts matching exception - Click `Add exception` - Note that the alerts table updates --- .../events_viewer/events_viewer.test.tsx | 42 ++++++++++++++ .../events_viewer/events_viewer.tsx | 6 +- .../common/components/events_viewer/index.tsx | 6 +- .../components/alerts_table/index.tsx | 55 +++++++++++++------ 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 833688ae57993..6f77d15913d07 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -50,6 +50,10 @@ const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
); +const exceptionsModal = (refetch: inputsModel.Refetch) => ( +
+); + const eventsViewerDefaultProps = { browserFields: {}, columns: [], @@ -460,4 +464,42 @@ describe('EventsViewer', () => { }); }); }); + + describe('exceptions modal', () => { + test('it renders exception modal if "exceptionsModal" callback exists', async () => { + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeTruthy(); + }); + }); + + test('it does not render exception modal if "exceptionModal" callback does not exist', async () => { + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeFalsy(); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 436386077e725..ebda64efabf65 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -109,6 +109,7 @@ interface Props { utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; // If truthy, the graph viewer (Resolver) is showing graphEventId: string | undefined; + exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode; } const EventsViewerComponent: React.FC = ({ @@ -134,6 +135,7 @@ const EventsViewerComponent: React.FC = ({ toggleColumn, utilityBar, graphEventId, + exceptionsModal, }) => { const { globalFullScreen } = useFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; @@ -259,6 +261,7 @@ const EventsViewerComponent: React.FC = ({ )} + {exceptionsModal && exceptionsModal(refetch)} {utilityBar && !resolverIsShowing(graphEventId) && ( {utilityBar?.(refetch, totalCountMinusDeleted)} )} @@ -335,5 +338,6 @@ export const EventsViewer = React.memo( prevProps.start === nextProps.start && prevProps.sort === nextProps.sort && prevProps.utilityBar === nextProps.utilityBar && - prevProps.graphEventId === nextProps.graphEventId + prevProps.graphEventId === nextProps.graphEventId && + prevProps.exceptionsModal === nextProps.exceptionsModal ); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 1563eab6039a6..ec56a3a1bd8d3 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -43,6 +43,7 @@ export interface OwnProps { headerFilterGroup?: React.ReactNode; pageFilters?: Filter[]; utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; + exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode; } type Props = OwnProps & PropsFromRedux; @@ -74,6 +75,7 @@ const StatefulEventsViewerComponent: React.FC = ({ utilityBar, // If truthy, the graph viewer (Resolver) is showing graphEventId, + exceptionsModal, }) => { const [ { docValueFields, browserFields, indexPatterns, isLoading: isLoadingIndexPattern }, @@ -156,6 +158,7 @@ const StatefulEventsViewerComponent: React.FC = ({ toggleColumn={toggleColumn} utilityBar={utilityBar} graphEventId={graphEventId} + exceptionsModal={exceptionsModal} /> @@ -241,6 +244,7 @@ export const StatefulEventsViewer = connector( prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.start === nextProps.start && prevProps.utilityBar === nextProps.utilityBar && - prevProps.graphEventId === nextProps.graphEventId + prevProps.graphEventId === nextProps.graphEventId && + prevProps.exceptionsModal === nextProps.exceptionsModal ) ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 07e69d850f173..854565ace9b4b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -228,7 +228,7 @@ export const AlertsTableComponent: React.FC = ({ exceptionListType, alertData, }: AddExceptionModalBaseProps) => { - if (alertData !== null && alertData !== undefined) { + if (alertData != null) { setShouldShowAddExceptionModal(true); setAddExceptionModalState({ ruleName, @@ -441,9 +441,43 @@ export const AlertsTableComponent: React.FC = ({ closeAddExceptionModal(); }, [closeAddExceptionModal]); - const onAddExceptionConfirm = useCallback(() => closeAddExceptionModal(), [ - closeAddExceptionModal, - ]); + const onAddExceptionConfirm = useCallback( + (refetch: inputsModel.Refetch) => (): void => { + refetch(); + closeAddExceptionModal(); + }, + [closeAddExceptionModal] + ); + + // Callback for creating the AddExceptionModal and allowing it + // access to the refetchQuery to update the page + const exceptionModalCallback = useCallback( + (refetchQuery: inputsModel.Refetch) => { + if (shouldShowAddExceptionModal) { + return ( + + ); + } else { + return <>; + } + }, + [ + addExceptionModalState, + filterGroup, + onAddExceptionCancel, + onAddExceptionConfirm, + shouldShowAddExceptionModal, + ] + ); if (loading || indexPatternsLoading || isEmpty(signalsIndex)) { return ( @@ -465,19 +499,8 @@ export const AlertsTableComponent: React.FC = ({ id={timelineId} start={from} utilityBar={utilityBarCallback} + exceptionsModal={exceptionModalCallback} /> - {shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null && ( - - )} ); }; From 44a017b2e7636e957175355527818d79d80bb323 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 27 Aug 2020 23:23:42 -0400 Subject: [PATCH 33/39] Fix more broken usages of `bulkCreate` (#76005) (#76131) --- .../tutorial/saved_objects_installer.js | 9 +++++--- .../tutorial/saved_objects_installer.test.js | 21 +++++++++++++++++++ .../models/data_recognizer/data_recognizer.ts | 7 ++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js index 790c6d9c2574e..dd63827c38c5d 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js @@ -62,9 +62,12 @@ class SavedObjectsInstallerUi extends React.Component { let resp; try { - resp = await this.props.bulkCreate(this.props.savedObjects, { - overwrite: this.state.overwrite, - }); + // Filter out the saved object version field, if present, to avoid inadvertently triggering optimistic concurrency control. + const objectsToCreate = this.props.savedObjects.map( + // eslint-disable-next-line no-unused-vars + ({ version, ...savedObject }) => savedObject + ); + resp = await this.props.bulkCreate(objectsToCreate, { overwrite: this.state.overwrite }); } catch (error) { if (!this._isMounted) { return; diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js index 6cc02184fbc16..e7b7d8ed1d7fd 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js @@ -79,4 +79,25 @@ describe('bulkCreate', () => { expect(component).toMatchSnapshot(); }); + + test('should filter out saved object version before calling bulkCreate', async () => { + const bulkCreateMock = jest.fn().mockResolvedValue({ + savedObjects: [savedObject], + }); + const component = mountWithIntl( + + ); + + findTestSubject(component, 'loadSavedObjects').simulate('click'); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(bulkCreateMock).toHaveBeenCalledWith([savedObject], expect.any(Object)); + }); }); diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 21e178dcc7e76..25c1892c4706c 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -20,6 +20,7 @@ import { getAuthorizationHeader } from '../../lib/request_authorization'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { KibanaObjects, + KibanaObjectConfig, ModuleDataFeed, ModuleJob, Module, @@ -100,7 +101,7 @@ interface ObjectExistResponse { id: string; type: string; exists: boolean; - savedObject?: any; + savedObject?: { id: string; type: string; attributes: KibanaObjectConfig }; } interface SaveResults { @@ -678,14 +679,14 @@ export class DataRecognizer { let results = { saved_objects: [] as any[] }; const filteredSavedObjects = objectExistResults .filter((o) => o.exists === false) - .map((o) => o.savedObject); + .map((o) => o.savedObject!); if (filteredSavedObjects.length) { results = await this.savedObjectsClient.bulkCreate( // Add an empty migrationVersion attribute to each saved object to ensure // it is automatically migrated to the 7.0+ format with a references attribute. filteredSavedObjects.map((doc) => ({ ...doc, - migrationVersion: doc.migrationVersion || {}, + migrationVersion: {}, })) ); } From 2eb8e69582152528239693c3d3cc757564ae2a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Mon, 31 Aug 2020 13:15:39 +0200 Subject: [PATCH 34/39] [Mappings editor] Fix bug when trying to clear the mapping field type (#76263) --- .../document_fields/field_parameters/type_parameter.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx index 46e70bf8e56ba..31ae37c82a43e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx @@ -70,7 +70,13 @@ export const TypeParameter = ({ isMultiField, isRootLevelField, showDocLink = fa : filterTypesForNonRootFields(FIELD_TYPES_OPTIONS) } selectedOptions={typeField.value} - onChange={typeField.setValue} + onChange={(value) => { + if (value.length === 0) { + // Don't allow clearing the type. One must always be selected + return; + } + typeField.setValue(value); + }} isClearable={false} data-test-subj="fieldType" /> From bbb51b5dcbfb2b324aa596b35a6a0ac3a4465eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Mon, 31 Aug 2020 10:47:51 -0400 Subject: [PATCH 35/39] [7.9] Revert add support for version on create & bulkCreate when overwriting a document (#76280) * Revert "Fix more broken usages of `bulkCreate` (#76005) (#76131)" This reverts commit 44a017b2e7636e957175355527818d79d80bb323. * Revert "Filter saved object `version` during legacy import (#75597) (#75793)" This reverts commit 6e82885aea30227bc87b1208fb2018141f7dd4ac. * Revert "[7.9] [Saved objects] Add support for version on create & bulkCreate when overwriting a document (#75172) (#75498)" This reverts commit 00836e5670a1f57253fe5a0dfe641f7406c64fc6. --- ...ore-server.savedobjectsbulkcreateobject.md | 1 - ...er.savedobjectsbulkcreateobject.version.md | 11 ----- ...n-core-server.savedobjectscreateoptions.md | 1 - ...erver.savedobjectscreateoptions.version.md | 13 ------ .../import/import_saved_objects.ts | 7 +--- .../import/resolve_import_errors.ts | 12 ++---- .../service/lib/repository.test.js | 42 +------------------ .../saved_objects/service/lib/repository.ts | 12 +----- .../service/saved_objects_client.ts | 6 --- src/core/server/server.api.md | 3 -- .../tutorial/saved_objects_installer.js | 9 ++-- .../tutorial/saved_objects_installer.test.js | 21 ---------- .../services/sample_data/routes/install.ts | 2 +- .../lib/import/import_dashboards.test.ts | 10 +---- .../server/lib/import/import_dashboards.ts | 3 +- .../services/epm/kibana/assets/install.ts | 4 +- .../models/data_recognizer/data_recognizer.ts | 7 ++-- 17 files changed, 19 insertions(+), 145 deletions(-) delete mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md index 5ccad134248f6..5a9ca36ba56f4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md @@ -20,5 +20,4 @@ export interface SavedObjectsBulkCreateObject | [migrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [references](./kibana-plugin-core-server.savedobjectsbulkcreateobject.references.md) | SavedObjectReference[] | | | [type](./kibana-plugin-core-server.savedobjectsbulkcreateobject.type.md) | string | | -| [version](./kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md) | string | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md deleted file mode 100644 index ca2a38693d036..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) > [version](./kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md) - -## SavedObjectsBulkCreateObject.version property - -Signature: - -```typescript -version?: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md index c5201efd0608d..5e9433c5c9196 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md @@ -20,5 +20,4 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions | [overwrite](./kibana-plugin-core-server.savedobjectscreateoptions.overwrite.md) | boolean | Overwrite existing documents (defaults to false) | | [references](./kibana-plugin-core-server.savedobjectscreateoptions.references.md) | SavedObjectReference[] | | | [refresh](./kibana-plugin-core-server.savedobjectscreateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | -| [version](./kibana-plugin-core-server.savedobjectscreateoptions.version.md) | string | An opaque version number which changes on each successful write operation. Can be used in conjunction with overwrite for implementing optimistic concurrency control. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md deleted file mode 100644 index 51da57064abb9..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) > [version](./kibana-plugin-core-server.savedobjectscreateoptions.version.md) - -## SavedObjectsCreateOptions.version property - -An opaque version number which changes on each successful write operation. Can be used in conjunction with `overwrite` for implementing optimistic concurrency control. - -Signature: - -```typescript -version?: string; -``` diff --git a/src/core/server/saved_objects/import/import_saved_objects.ts b/src/core/server/saved_objects/import/import_saved_objects.ts index 4956491a79aa9..6065e03fb1628 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.ts @@ -25,7 +25,6 @@ import { SavedObjectsImportOptions, } from './types'; import { validateReferences } from './validate_references'; -import { SavedObject } from '../types'; /** * Import saved objects from given stream. See the {@link SavedObjectsImportOptions | options} for more @@ -68,7 +67,7 @@ export async function importSavedObjectsFromStream({ } // Create objects in bulk - const bulkCreateResult = await savedObjectsClient.bulkCreate(omitVersion(filteredObjects), { + const bulkCreateResult = await savedObjectsClient.bulkCreate(filteredObjects, { overwrite, namespace, }); @@ -83,7 +82,3 @@ export async function importSavedObjectsFromStream({ ...(errorAccumulator.length ? { errors: errorAccumulator } : {}), }; } - -export function omitVersion(objects: SavedObject[]): SavedObject[] { - return objects.map(({ version, ...object }) => object); -} diff --git a/src/core/server/saved_objects/import/resolve_import_errors.ts b/src/core/server/saved_objects/import/resolve_import_errors.ts index dce044a31a577..a5175aa080598 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.ts @@ -26,7 +26,6 @@ import { SavedObjectsResolveImportErrorsOptions, } from './types'; import { validateReferences } from './validate_references'; -import { omitVersion } from './import_saved_objects'; /** * Resolve and return saved object import errors. @@ -92,7 +91,7 @@ export async function resolveSavedObjectsImportErrors({ // Bulk create in two batches, overwrites and non-overwrites const { objectsToOverwrite, objectsToNotOverwrite } = splitOverwrites(filteredObjects, retries); if (objectsToOverwrite.length) { - const bulkCreateResult = await savedObjectsClient.bulkCreate(omitVersion(objectsToOverwrite), { + const bulkCreateResult = await savedObjectsClient.bulkCreate(objectsToOverwrite, { overwrite: true, namespace, }); @@ -103,12 +102,9 @@ export async function resolveSavedObjectsImportErrors({ successCount += bulkCreateResult.saved_objects.filter((obj) => !obj.error).length; } if (objectsToNotOverwrite.length) { - const bulkCreateResult = await savedObjectsClient.bulkCreate( - omitVersion(objectsToNotOverwrite), - { - namespace, - } - ); + const bulkCreateResult = await savedObjectsClient.bulkCreate(objectsToNotOverwrite, { + namespace, + }); errorAccumulator = [ ...errorAccumulator, ...extractErrors(bulkCreateResult.saved_objects, objectsToNotOverwrite), diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index b470ddf8b8cc4..d563edbe66c9b 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -472,16 +472,8 @@ describe('SavedObjectsRepository', () => { { method, _index = expect.any(String), getId = () => expect.any(String) } ) => { const body = []; - for (const { type, id, if_primary_term: ifPrimaryTerm, if_seq_no: ifSeqNo } of objects) { - body.push({ - [method]: { - _index, - _id: getId(type, id), - ...(ifPrimaryTerm && ifSeqNo - ? { if_primary_term: expect.any(Number), if_seq_no: expect.any(Number) } - : {}), - }, - }); + for (const { type, id } of objects) { + body.push({ [method]: { _index, _id: getId(type, id) } }); body.push(expect.any(Object)); } expectClusterCallArgs({ body }); @@ -537,27 +529,6 @@ describe('SavedObjectsRepository', () => { expectClusterCallArgsAction([obj1, obj2], { method: 'index' }); }); - it(`should use the ES index method with version if ID and version are defined and overwrite=true`, async () => { - await bulkCreateSuccess( - [ - { - ...obj1, - version: mockVersion, - }, - obj2, - ], - { overwrite: true } - ); - - const obj1WithSeq = { - ...obj1, - if_seq_no: mockVersionProps._seq_no, - if_primary_term: mockVersionProps._primary_term, - }; - - expectClusterCallArgsAction([obj1WithSeq, obj2], { method: 'index' }); - }); - it(`should use the ES create method if ID is defined and overwrite=false`, async () => { await bulkCreateSuccess([obj1, obj2]); expectClusterCallArgsAction([obj1, obj2], { method: 'create' }); @@ -1487,15 +1458,6 @@ describe('SavedObjectsRepository', () => { expectClusterCalls('index'); }); - it(`should use the ES index with version if ID and version are defined and overwrite=true`, async () => { - await createSuccess(type, attributes, { id, overwrite: true, version: mockVersion }); - expectClusterCalls('index'); - expectClusterCallArgs({ - if_seq_no: mockVersionProps._seq_no, - if_primary_term: mockVersionProps._primary_term, - }); - }); - it(`should use the ES create action if ID is defined and overwrite=false`, async () => { await createSuccess(type, attributes, { id }); expectClusterCalls('create'); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 173c0c34b9457..7a5ac9204627c 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -220,7 +220,6 @@ export class SavedObjectsRepository { overwrite = false, references = [], refresh = DEFAULT_REFRESH_SETTING, - version, } = options; if (!this._allowedTypes.includes(type)) { @@ -261,7 +260,6 @@ export class SavedObjectsRepository { index: this.getIndexForType(type), refresh, body: raw._source, - ...(overwrite && version ? decodeRequestVersion(version) : {}), }); return this._rawToSavedObject({ @@ -341,12 +339,7 @@ export class SavedObjectsRepository { let savedObjectNamespace; let savedObjectNamespaces; - let versionProperties; - const { - esRequestIndex, - object: { version, ...object }, - method, - } = expectedBulkGetResult.value; + const { esRequestIndex, object, method } = expectedBulkGetResult.value; if (esRequestIndex !== undefined) { const indexFound = bulkGetResponse.status !== 404; const actualResult = indexFound ? bulkGetResponse.docs[esRequestIndex] : undefined; @@ -363,14 +356,12 @@ export class SavedObjectsRepository { }; } savedObjectNamespaces = getSavedObjectNamespaces(namespace, docFound && actualResult); - versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(object.type)) { savedObjectNamespace = namespace; } else if (this._registry.isMultiNamespace(object.type)) { savedObjectNamespaces = getSavedObjectNamespaces(namespace); } - versionProperties = getExpectedVersionProperties(version); } const expectedResult = { @@ -395,7 +386,6 @@ export class SavedObjectsRepository { [method]: { _id: expectedResult.rawMigratedDoc._id, _index: this.getIndexForType(object.type), - ...(overwrite && versionProperties), }, }, expectedResult.rawMigratedDoc._source diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 6a9f4f5143e84..e15a92c92772f 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -37,11 +37,6 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { id?: string; /** Overwrite existing documents (defaults to false) */ overwrite?: boolean; - /** - * An opaque version number which changes on each successful write operation. - * Can be used in conjunction with `overwrite` for implementing optimistic concurrency control. - **/ - version?: string; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; references?: SavedObjectReference[]; @@ -57,7 +52,6 @@ export interface SavedObjectsBulkCreateObject { id?: string; type: string; attributes: T; - version?: string; references?: SavedObjectReference[]; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index f198ac53be16a..c3cd219f2b8ec 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1945,8 +1945,6 @@ export interface SavedObjectsBulkCreateObject { references?: SavedObjectReference[]; // (undocumented) type: string; - // (undocumented) - version?: string; } // @public (undocumented) @@ -2081,7 +2079,6 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { // (undocumented) references?: SavedObjectReference[]; refresh?: MutatingOperationRefreshSetting; - version?: string; } // @public (undocumented) diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js index dd63827c38c5d..790c6d9c2574e 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.js @@ -62,12 +62,9 @@ class SavedObjectsInstallerUi extends React.Component { let resp; try { - // Filter out the saved object version field, if present, to avoid inadvertently triggering optimistic concurrency control. - const objectsToCreate = this.props.savedObjects.map( - // eslint-disable-next-line no-unused-vars - ({ version, ...savedObject }) => savedObject - ); - resp = await this.props.bulkCreate(objectsToCreate, { overwrite: this.state.overwrite }); + resp = await this.props.bulkCreate(this.props.savedObjects, { + overwrite: this.state.overwrite, + }); } catch (error) { if (!this._isMounted) { return; diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js index e7b7d8ed1d7fd..6cc02184fbc16 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js @@ -79,25 +79,4 @@ describe('bulkCreate', () => { expect(component).toMatchSnapshot(); }); - - test('should filter out saved object version before calling bulkCreate', async () => { - const bulkCreateMock = jest.fn().mockResolvedValue({ - savedObjects: [savedObject], - }); - const component = mountWithIntl( - - ); - - findTestSubject(component, 'loadSavedObjects').simulate('click'); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - - expect(bulkCreateMock).toHaveBeenCalledWith([savedObject], expect.any(Object)); - }); }); diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index b94456682afcc..2d1a53fbb09dc 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -154,7 +154,7 @@ export function createInstallRoute( let createResults; try { createResults = await context.core.savedObjects.client.bulkCreate( - sampleDataset.savedObjects.map(({ version, ...savedObject }) => savedObject), + sampleDataset.savedObjects, { overwrite: true } ); } catch (err) { diff --git a/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts index 37e00a8c67fe3..9d4dbb6067946 100644 --- a/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts @@ -30,18 +30,12 @@ describe('importDashboards(req)', () => { savedObjectClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); importedObjects = [ - { - id: 'dashboard-01', - type: 'dashboard', - attributes: { panelJSON: '{}' }, - references: [], - version: 'foo', - }, + { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' }, references: [] }, { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' }, references: [] }, ]; }); - test('should call bulkCreate with each asset, filtering out any version if present', async () => { + test('should call bulkCreate with each asset', async () => { await importDashboards(savedObjectClient, importedObjects, { overwrite: false, exclude: [] }); expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); diff --git a/src/plugins/legacy_export/server/lib/import/import_dashboards.ts b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts index 8c9eb2fac61af..7b7562aecd7bd 100644 --- a/src/plugins/legacy_export/server/lib/import/import_dashboards.ts +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts @@ -31,8 +31,7 @@ export async function importDashboards( // docs are not seen as automatically up-to-date. const docs = objects .filter((item) => !exclude.includes(item.type)) - // filter out any document version, if present - .map(({ version, ...doc }) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); + .map((doc) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); const results = await savedObjectsClient.bulkCreate(docs, { overwrite }); return { objects: results.saved_objects }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts index 26e30948abfa7..a3fe444b19b1a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts @@ -20,9 +20,7 @@ import { import { deleteKibanaSavedObjectsAssets } from '../../packages/remove'; import { getInstallationObject, savedObjectTypes } from '../../packages'; -type SavedObjectToBe = Required> & { - type: AssetType; -}; +type SavedObjectToBe = Required & { type: AssetType }; export type ArchiveAsset = Pick< SavedObject, 'id' | 'attributes' | 'migrationVersion' | 'references' diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 25c1892c4706c..21e178dcc7e76 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -20,7 +20,6 @@ import { getAuthorizationHeader } from '../../lib/request_authorization'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { KibanaObjects, - KibanaObjectConfig, ModuleDataFeed, ModuleJob, Module, @@ -101,7 +100,7 @@ interface ObjectExistResponse { id: string; type: string; exists: boolean; - savedObject?: { id: string; type: string; attributes: KibanaObjectConfig }; + savedObject?: any; } interface SaveResults { @@ -679,14 +678,14 @@ export class DataRecognizer { let results = { saved_objects: [] as any[] }; const filteredSavedObjects = objectExistResults .filter((o) => o.exists === false) - .map((o) => o.savedObject!); + .map((o) => o.savedObject); if (filteredSavedObjects.length) { results = await this.savedObjectsClient.bulkCreate( // Add an empty migrationVersion attribute to each saved object to ensure // it is automatically migrated to the 7.0+ format with a references attribute. filteredSavedObjects.map((doc) => ({ ...doc, - migrationVersion: {}, + migrationVersion: doc.migrationVersion || {}, })) ); } From 1898b38efb939b74c316bbda94af9a990d08a6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Mon, 31 Aug 2020 13:13:40 -0400 Subject: [PATCH 36/39] [7.9] Fix alerts unable to create / update when the name has trailing whitepace(s) (#76079) (#76172) * Merge with master * Fix some references * Fix test failures Co-authored-by: Elastic Machine --- .../alerts/server/alerts_client.test.ts | 151 +++++++++++++++++- x-pack/plugins/alerts/server/alerts_client.ts | 4 +- .../tests/alerting/create.ts | 33 ++++ .../tests/alerting/update.ts | 45 ++++++ 4 files changed, 226 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts index d69d04f71ce9e..9f215df161bc2 100644 --- a/x-pack/plugins/alerts/server/alerts_client.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client.test.ts @@ -508,6 +508,70 @@ describe('create()', () => { expect(taskManager.schedule).toHaveBeenCalledTimes(0); }); + test('should trim alert name when creating API key', async () => { + const data = getMockData({ name: ' my alert name ' }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actions: [], + actionTypeId: 'test', + }, + references: [], + }, + ], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: false, + name: ' my alert name ', + alertTypeId: '123', + schedule: { interval: 10000 }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + + await alertsClient.create({ data }); + expect(alertsClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: 123/my alert name'); + }); + test('should validate params', async () => { const data = getMockData(); alertTypeRegistry.get.mockReturnValue({ @@ -1864,8 +1928,24 @@ describe('update()', () => { type: 'alert', attributes: { enabled: true, - alertTypeId: '123', + tags: ['foo'], + alertTypeId: 'myType', + schedule: { interval: '10s' }, + consumer: 'myApp', scheduledTaskId: 'task-123', + params: {}, + throttle: null, + actions: [ + { + group: 'default', + id: '1', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], }, references: [], version: '123', @@ -1883,7 +1963,7 @@ describe('update()', () => { savedObjectsClient.get.mockResolvedValue(existingAlert); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedAlert); alertTypeRegistry.get.mockReturnValue({ - id: '123', + id: 'myType', name: 'Test', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', @@ -2062,9 +2142,10 @@ describe('update()', () => { }, }, ], - "alertTypeId": "123", + "alertTypeId": "myType", "apiKey": null, "apiKeyOwner": null, + "consumer": "myApp", "enabled": true, "name": "abc", "params": Object { @@ -2217,9 +2298,10 @@ describe('update()', () => { }, }, ], - "alertTypeId": "123", + "alertTypeId": "myType", "apiKey": "MTIzOmFiYw==", "apiKeyOwner": "elastic", + "consumer": "myApp", "enabled": true, "name": "abc", "params": Object { @@ -2366,9 +2448,10 @@ describe('update()', () => { }, }, ], - "alertTypeId": "123", + "alertTypeId": "myType", "apiKey": null, "apiKeyOwner": null, + "consumer": "myApp", "enabled": false, "name": "abc", "params": Object { @@ -2440,6 +2523,64 @@ describe('update()', () => { ); }); + it('should trim alert name in the API key name', async () => { + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actions: [], + actionTypeId: 'test', + }, + references: [], + }, + ], + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: false, + name: ' my alert name ', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + scheduledTaskId: 'task-123', + apiKey: null, + }, + updated_at: new Date().toISOString(), + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + await alertsClient.update({ + id: '1', + data: { + ...existingAlert.attributes, + name: ' my alert name ', + }, + }); + + expect(alertsClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: myType/my alert name'); + }); + it('swallows error when invalidate API key throws', async () => { alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); savedObjectsClient.bulkGet.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts index 61e929d325066..c2b169415d35c 100644 --- a/x-pack/plugins/alerts/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { omit, isEqual, map, truncate } from 'lodash'; +import { omit, isEqual, map, truncate, trim } from 'lodash'; import { i18n } from '@kbn/i18n'; import { Logger, @@ -719,6 +719,6 @@ export class AlertsClient { } private generateAPIKeyName(alertTypeId: string, alertName: string) { - return truncate(`Alerting: ${alertTypeId}/${alertName}`, { length: 256 }); + return truncate(`Alerting: ${alertTypeId}/${trim(alertName)}`, { length: 256 }); } } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 4ca943f3e188a..899aa4c56c798 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -153,6 +153,39 @@ export default function createAlertTests({ getService }: FtrProviderContext) { } }); + it('should handle create alert request appropriately when alert name has leading and trailing whitespaces', async () => { + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send( + getTestAlertData({ + name: ' leading and trailing whitespace ', + }) + ); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'global_read at space1': + case 'space_1_all at space2': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + expect(response.body.name).to.eql(' leading and trailing whitespace '); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it('should handle create alert request appropriately when alert type is unregistered', async () => { const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 2bcc035beb7a9..04b43a7ad63ab 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -191,6 +191,51 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { } }); + it('should handle update alert request appropriately when alert name has leading and trailing whitespaces', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + + const updatedData = { + name: ' leading and trailing whitespace ', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '12s' }, + actions: [], + throttle: '1m', + }; + const response = await supertestWithoutAuth + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send(updatedData); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + expect(response.body.name).to.eql(' leading and trailing whitespace '); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) From 6655cb5b702d6165db3510769c80c1a3a38ddcde Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 31 Aug 2020 23:14:21 +0200 Subject: [PATCH 37/39] [Snapshot & Restore] Decode URIs and fix editing of a policy (#76278) (#76320) * fix URI decoding and editing of a policy which backs up all indices * fix type issue * fix general use of encoding and update decode algo * fix serialisation of snapshots and added a test * Fix test description name * Update attempt_to_uri_decode.ts * catch errors from decoding in the already throwing code Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../common/lib/snapshot_serialization.test.ts | 203 ++++++++++-------- .../common/lib/snapshot_serialization.ts | 4 +- .../snapshot_restore/common/lib/utils.ts | 4 +- .../collapsible_lists/use_collapsible_list.ts | 2 +- .../policy_form/steps/step_retention.tsx | 14 +- .../indices_and_data_streams_field.tsx | 16 +- .../steps/step_settings/step_settings.tsx | 14 +- .../steps/step_logistics/step_logistics.tsx | 2 +- .../application/lib/attempt_to_uri_decode.ts | 22 ++ .../public/application/lib/index.ts | 7 + .../application/lib/use_decoded_params.ts | 23 ++ .../public/application/sections/home/home.tsx | 2 +- .../policy_details/policy_details.tsx | 4 +- .../sections/home/policy_list/policy_list.tsx | 9 +- .../home/repository_list/repository_list.tsx | 11 +- .../home/snapshot_list/snapshot_list.tsx | 5 +- .../sections/policy_add/policy_add.tsx | 2 +- .../sections/policy_edit/policy_edit.tsx | 9 +- .../repository_add/repository_add.tsx | 6 +- .../repository_edit/repository_edit.tsx | 9 +- .../restore_snapshot/restore_snapshot.tsx | 5 +- .../application/services/navigation/links.ts | 22 +- .../application/services/validation/index.ts | 2 +- .../services/validation/validate_policy.ts | 43 +++- 24 files changed, 277 insertions(+), 163 deletions(-) create mode 100644 x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts create mode 100644 x-pack/plugins/snapshot_restore/public/application/lib/index.ts create mode 100644 x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts index 473a3392deb3e..e5d8e804691a8 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts @@ -4,55 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { deserializeSnapshotDetails } from './snapshot_serialization'; +import { deserializeSnapshotDetails, serializeSnapshotConfig } from './snapshot_serialization'; -describe('deserializeSnapshotDetails', () => { - test('deserializes a snapshot', () => { - expect( - deserializeSnapshotDetails( - 'repositoryName', - { - snapshot: 'snapshot name', - uuid: 'UUID', - version_id: 5, - version: 'version', - indices: ['index2', 'index3', 'index1'], - include_global_state: false, - state: 'SUCCESS', - start_time: '0', - start_time_in_millis: 0, - end_time: '1', - end_time_in_millis: 1, - duration_in_millis: 1, - shards: { - total: 3, - failed: 1, - successful: 2, - }, - failures: [ - { - index: 'z', - shard: 1, - }, - { - index: 'a', - shard: 3, - }, - { - index: 'a', - shard: 1, - }, - { - index: 'a', - shard: 2, - }, - ], - }, - 'found-snapshots', - [ +describe('Snapshot serialization and deserialization', () => { + describe('deserializeSnapshotDetails', () => { + test('deserializes a snapshot', () => { + expect( + deserializeSnapshotDetails( + 'repositoryName', { - snapshot: 'last_successful_snapshot', - uuid: 'last_successful_snapshot_UUID', + snapshot: 'snapshot name', + uuid: 'UUID', version_id: 5, version: 'version', indices: ['index2', 'index3', 'index1'], @@ -87,56 +49,109 @@ describe('deserializeSnapshotDetails', () => { }, ], }, - ] - ) - ).toEqual({ - repository: 'repositoryName', - snapshot: 'snapshot name', - uuid: 'UUID', - versionId: 5, - version: 'version', - // Indices are sorted. - indices: ['index1', 'index2', 'index3'], - dataStreams: [], - includeGlobalState: false, - // Failures are grouped and sorted by index, and the failures themselves are sorted by shard. - indexFailures: [ - { - index: 'a', - failures: [ + 'found-snapshots', + [ { - shard: 1, - }, - { - shard: 2, - }, - { - shard: 3, - }, - ], - }, - { - index: 'z', - failures: [ - { - shard: 1, + snapshot: 'last_successful_snapshot', + uuid: 'last_successful_snapshot_UUID', + version_id: 5, + version: 'version', + indices: ['index2', 'index3', 'index1'], + include_global_state: false, + state: 'SUCCESS', + start_time: '0', + start_time_in_millis: 0, + end_time: '1', + end_time_in_millis: 1, + duration_in_millis: 1, + shards: { + total: 3, + failed: 1, + successful: 2, + }, + failures: [ + { + index: 'z', + shard: 1, + }, + { + index: 'a', + shard: 3, + }, + { + index: 'a', + shard: 1, + }, + { + index: 'a', + shard: 2, + }, + ], }, - ], + ] + ) + ).toEqual({ + repository: 'repositoryName', + snapshot: 'snapshot name', + uuid: 'UUID', + versionId: 5, + version: 'version', + // Indices are sorted. + indices: ['index1', 'index2', 'index3'], + dataStreams: [], + includeGlobalState: false, + // Failures are grouped and sorted by index, and the failures themselves are sorted by shard. + indexFailures: [ + { + index: 'a', + failures: [ + { + shard: 1, + }, + { + shard: 2, + }, + { + shard: 3, + }, + ], + }, + { + index: 'z', + failures: [ + { + shard: 1, + }, + ], + }, + ], + state: 'SUCCESS', + startTime: '0', + startTimeInMillis: 0, + endTime: '1', + endTimeInMillis: 1, + durationInMillis: 1, + shards: { + total: 3, + failed: 1, + successful: 2, }, - ], - state: 'SUCCESS', - startTime: '0', - startTimeInMillis: 0, - endTime: '1', - endTimeInMillis: 1, - durationInMillis: 1, - shards: { - total: 3, - failed: 1, - successful: 2, - }, - managedRepository: 'found-snapshots', - isLastSuccessfulSnapshot: false, + managedRepository: 'found-snapshots', + isLastSuccessfulSnapshot: false, + }); + }); + }); + + describe('serializeSnapshotConfig', () => { + test('serializes config as expected', () => { + const metadata = { test: 'what have you' }; + expect(serializeSnapshotConfig({ metadata, indices: '.k*' })).toEqual({ + metadata, + indices: ['.k*'], + }); + }); + test('serializes empty config as expected', () => { + expect(serializeSnapshotConfig({})).toEqual({}); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts index a85b49430eecd..61a57c9288f03 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts @@ -131,10 +131,10 @@ export function deserializeSnapshotConfig(snapshotConfigEs: SnapshotConfigEs): S export function serializeSnapshotConfig(snapshotConfig: SnapshotConfig): SnapshotConfigEs { const { indices, ignoreUnavailable, includeGlobalState, partial, metadata } = snapshotConfig; - const indicesArray = csvToArray(indices); + const maybeIndicesArray = csvToArray(indices); const snapshotConfigEs: SnapshotConfigEs = { - indices: indicesArray, + indices: maybeIndicesArray, ignore_unavailable: ignoreUnavailable, include_global_state: includeGlobalState, partial, diff --git a/x-pack/plugins/snapshot_restore/common/lib/utils.ts b/x-pack/plugins/snapshot_restore/common/lib/utils.ts index 96eb7cb6908d8..3d07aa8de3a55 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/utils.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/utils.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export const csvToArray = (indices?: string | string[]): string[] => { +export const csvToArray = (indices?: string | string[]): string[] | undefined => { return indices && Array.isArray(indices) ? indices : typeof indices === 'string' ? indices.split(',') - : []; + : undefined; }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts index 275915c5760af..7ec34dd7b3984 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts +++ b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts @@ -24,7 +24,7 @@ const maximumItemPreviewCount = 10; export const useCollapsibleList = ({ items }: Arg): ReturnValue => { const [isShowingFullList, setIsShowingFullList] = useState(false); - const itemsArray = csvToArray(items); + const itemsArray = csvToArray(items) ?? []; const displayItems: ChildItems = items === undefined ? 'all' diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx index ae22464c8a52b..407b9be14e3c1 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx @@ -35,11 +35,17 @@ export const PolicyStepRetention: React.FunctionComponent = ({ }) => { const { retention = {} } = policy; - const updatePolicyRetention = (updatedFields: Partial): void => { + const updatePolicyRetention = ( + updatedFields: Partial, + validationHelperData = {} + ): void => { const newRetention = { ...retention, ...updatedFields }; - updatePolicy({ - retention: newRetention, - }); + updatePolicy( + { + retention: newRetention, + }, + validationHelperData + ); }; // State for touched inputs diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx index 94854905e6686..6f89427516453 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx @@ -25,7 +25,7 @@ import { import { SlmPolicyPayload } from '../../../../../../../../common/types'; import { useServices } from '../../../../../../app_context'; -import { PolicyValidation } from '../../../../../../services/validation'; +import { PolicyValidation, ValidatePolicyHelperData } from '../../../../../../services/validation'; import { orderDataStreamsAndIndices } from '../../../../../lib'; import { DataStreamBadge } from '../../../../../data_stream_badge'; @@ -34,12 +34,16 @@ import { mapSelectionToIndicesOptions, determineListMode } from './helpers'; import { DataStreamsAndIndicesListHelpText } from './data_streams_and_indices_list_help_text'; +interface IndicesConfig { + indices?: string[] | string; +} + interface Props { isManagedPolicy: boolean; policy: SlmPolicyPayload; indices: string[]; dataStreams: string[]; - onUpdate: (arg: { indices?: string[] | string }) => void; + onUpdate: (arg: IndicesConfig, validateHelperData: ValidatePolicyHelperData) => void; errors: PolicyValidation['errors']; } @@ -53,7 +57,7 @@ export const IndicesAndDataStreamsField: FunctionComponent = ({ dataStreams, indices, policy, - onUpdate, + onUpdate: _onUpdate, errors, }) => { const { i18n } = useServices(); @@ -66,6 +70,12 @@ export const IndicesAndDataStreamsField: FunctionComponent = ({ !config.indices || (Array.isArray(config.indices) && config.indices.length === 0) ); + const onUpdate = (data: IndicesConfig) => { + _onUpdate(data, { + validateIndicesCount: !isAllIndices, + }); + }; + const [indicesAndDataStreamsSelection, setIndicesAndDataStreamsSelection] = useState( () => Array.isArray(config.indices) && !isAllIndices diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx index 9d43c45d17ea7..f65156bada278 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx @@ -31,11 +31,17 @@ export const PolicyStepSettings: React.FunctionComponent = ({ }) => { const { config = {}, isManagedPolicy } = policy; - const updatePolicyConfig = (updatedFields: Partial): void => { + const updatePolicyConfig = ( + updatedFields: Partial, + validationHelperData = {} + ): void => { const newConfig = { ...config, ...updatedFields }; - updatePolicy({ - config: newConfig, - }); + updatePolicy( + { + config: newConfig, + }, + validationHelperData + ); }; const renderIgnoreUnavailableField = () => ( diff --git a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx index d9fd4cca0d614..855abb04b7c2b 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx @@ -311,7 +311,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = }); } }} - selectedIndicesAndDataStreams={csvToArray(restoreIndices)} + selectedIndicesAndDataStreams={csvToArray(restoreIndices) ?? []} indices={snapshotIndices} dataStreams={snapshotDataStreams} /> diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts b/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts new file mode 100644 index 0000000000000..6f99fae118cd7 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const attemptToURIDecode = (value: string) => { + let result: string; + + try { + result = decodeURI(value); + result = decodeURIComponent(result); + } catch (e1) { + try { + result = decodeURIComponent(value); + } catch (e2) { + result = value; + } + } + + return result; +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts new file mode 100644 index 0000000000000..c544df4365606 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useDecodedParams } from './use_decoded_params'; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts b/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts new file mode 100644 index 0000000000000..d4582de4084ba --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useParams } from 'react-router-dom'; +import { attemptToURIDecode } from './attempt_to_uri_decode'; + +export const useDecodedParams = < + Params extends { [K in keyof Params]?: string } = {} +>(): Params => { + const params = useParams>(); + const decodedParams = {} as Params; + + for (const [key, value] of Object.entries(params)) { + if (value) { + (decodedParams as any)[key] = attemptToURIDecode(value); + } + } + + return decodedParams; +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx index 962dfa73e95c7..12e7e2de9383d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx @@ -89,7 +89,7 @@ export const SnapshotRestoreHome: React.FunctionComponent { - history.push(`${BASE_PATH}/${newSection}`); + history.push(encodeURI(`${BASE_PATH}/${encodeURIComponent(newSection)}`)); }; // Set breadcrumb and page title diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index 5959ad6441f5d..f67e8eb586238 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -24,6 +24,8 @@ import { EuiSpacer, } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; + import { SlmPolicy } from '../../../../../../common/types'; import { useServices } from '../../../../app_context'; import { SectionError, Error } from '../../../../../shared_imports'; @@ -41,8 +43,6 @@ import { } from '../../../../components'; import { TabSummary, TabHistory } from './tabs'; -import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; - interface Props { policyName: SlmPolicy['name']; onClose: () => void; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 39ef66eb0658c..655bd0e9d8bb9 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -9,6 +9,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + import { SectionError, Error, @@ -20,6 +22,7 @@ import { SlmPolicy } from '../../../../../common/types'; import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants'; +import { useDecodedParams } from '../../../lib'; import { useLoadPolicies, useLoadRetentionSettings } from '../../../services/http'; import { linkToAddPolicy, linkToPolicy } from '../../../services/navigation'; import { useServices } from '../../../app_context'; @@ -28,18 +31,14 @@ import { PolicyDetails } from './policy_details'; import { PolicyTable } from './policy_table'; import { PolicyRetentionSchedule } from './policy_retention_schedule'; -import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; - interface MatchParams { policyName?: SlmPolicy['name']; } export const PolicyList: React.FunctionComponent> = ({ - match: { - params: { policyName }, - }, history, }) => { + const { policyName } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx index f6b9ac08cc0a2..9afdad3806def 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx @@ -7,11 +7,14 @@ import React, { Fragment, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; - import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + import { Repository } from '../../../../../common/types'; import { SectionError, Error } from '../../../../shared_imports'; import { SectionLoading } from '../../../components'; +import { useDecodedParams } from '../../../lib'; import { BASE_PATH, UIM_REPOSITORY_LIST_LOAD } from '../../../constants'; import { useServices } from '../../../app_context'; import { useLoadRepositories } from '../../../services/http'; @@ -20,18 +23,14 @@ import { linkToAddRepository, linkToRepository } from '../../../services/navigat import { RepositoryDetails } from './repository_details'; import { RepositoryTable } from './repository_table'; -import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; - interface MatchParams { repositoryName?: Repository['name']; } export const RepositoryList: React.FunctionComponent> = ({ - match: { - params: { repositoryName }, - }, history, }) => { + const { repositoryName } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index 2b8df7294c374..d13188fc44730 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -24,6 +24,7 @@ import { linkToSnapshot, } from '../../../services/navigation'; import { useServices } from '../../../app_context'; +import { useDecodedParams } from '../../../lib'; import { SnapshotDetails } from './snapshot_details'; import { SnapshotTable } from './snapshot_table'; @@ -35,12 +36,10 @@ interface MatchParams { } export const SnapshotList: React.FunctionComponent> = ({ - match: { - params: { repositoryName, snapshotId }, - }, location: { search }, history, }) => { + const { repositoryName, snapshotId } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx index 90cd26c821c5e..dead9abb6ef0c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx @@ -43,7 +43,7 @@ export const PolicyAdd: React.FunctionComponent = ({ if (error) { setSaveError(error); } else { - history.push(`${BASE_PATH}/policies/${name}`); + history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`)); } }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx index 915b1cbef1233..7af663b29957d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx @@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; import { SectionError, Error } from '../../../shared_imports'; +import { useDecodedParams } from '../../lib'; import { TIME_UNITS } from '../../../../common/constants'; import { SectionLoading, PolicyForm } from '../../components'; import { BASE_PATH } from '../../constants'; @@ -22,12 +23,10 @@ interface MatchParams { } export const PolicyEdit: React.FunctionComponent> = ({ - match: { - params: { name }, - }, history, location: { pathname }, }) => { + const { name } = useDecodedParams(); const { i18n } = useServices(); // Set breadcrumb and page title @@ -83,12 +82,12 @@ export const PolicyEdit: React.FunctionComponent { - history.push(`${BASE_PATH}/policies/${name}`); + history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`)); }; const renderLoading = () => { diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx index 08bfde833c368..a66d137471eba 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx @@ -44,7 +44,11 @@ export const RepositoryAdd: React.FunctionComponent = ({ } else { const { redirect } = parse(search.replace(/^\?/, ''), { sort: false }); - history.push(redirect ? (redirect as string) : `${BASE_PATH}/${section}/${name}`); + history.push( + redirect + ? (redirect as string) + : encodeURI(`${BASE_PATH}/${encodeURIComponent(section)}/${encodeURIComponent(name)}`) + ); } }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx index 95f8b9b8bde7d..3e8cff5793572 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx @@ -16,18 +16,17 @@ import { BASE_PATH, Section } from '../../constants'; import { useServices } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { editRepository, useLoadRepository } from '../../services/http'; +import { useDecodedParams } from '../../lib'; interface MatchParams { name: string; } export const RepositoryEdit: React.FunctionComponent> = ({ - match: { - params: { name }, - }, history, }) => { const { i18n } = useServices(); + const { name } = useDecodedParams(); const section = 'repositories' as Section; // Set breadcrumb and page title @@ -70,7 +69,9 @@ export const RepositoryEdit: React.FunctionComponent> = ({ - match: { - params: { repositoryName, snapshotId }, - }, history, }) => { const { i18n } = useServices(); + const { repositoryName, snapshotId } = useDecodedParams(); // Set breadcrumb and page title useEffect(() => { diff --git a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts index 503704c6fe820..b498dc280d395 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts @@ -13,33 +13,37 @@ export function linkToRepositories() { } export function linkToRepository(repositoryName: string) { - return `/repositories/${encodeURIComponent(repositoryName)}`; + return encodeURI(`/repositories/${encodeURIComponent(repositoryName)}`); } export function linkToEditRepository(repositoryName: string) { - return `/edit_repository/${encodeURIComponent(repositoryName)}`; + return encodeURI(`/edit_repository/${encodeURIComponent(repositoryName)}`); } export function linkToAddRepository(redirect?: string) { - return `/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`; + return encodeURI(`/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`); } export function linkToSnapshots(repositoryName?: string, policyName?: string) { if (repositoryName) { - return `/snapshots?repository=${encodeURIComponent(repositoryName)}`; + return encodeURI(`/snapshots?repository=${encodeURIComponent(repositoryName)}`); } if (policyName) { - return `/snapshots?policy=${encodeURIComponent(policyName)}`; + return encodeURI(`/snapshots?policy=${encodeURIComponent(policyName)}`); } return `/snapshots`; } export function linkToSnapshot(repositoryName: string, snapshotName: string) { - return `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; + return encodeURI( + `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}` + ); } export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) { - return `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; + return encodeURI( + `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}` + ); } export function linkToPolicies() { @@ -47,11 +51,11 @@ export function linkToPolicies() { } export function linkToPolicy(policyName: string) { - return `/policies/${encodeURIComponent(policyName)}`; + return encodeURI(`/policies/${encodeURIComponent(policyName)}`); } export function linkToEditPolicy(policyName: string) { - return `/edit_policy/${encodeURIComponent(policyName)}`; + return encodeURI(`/edit_policy/${encodeURIComponent(policyName)}`); } export function linkToAddPolicy() { diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts index 7fd755497eec6..82bf0ef06c400 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts @@ -12,4 +12,4 @@ export { export { RestoreValidation, validateRestore } from './validate_restore'; -export { PolicyValidation, validatePolicy } from './validate_policy'; +export { PolicyValidation, validatePolicy, ValidatePolicyHelperData } from './validate_policy'; diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts index 24960b2533230..4314b703722f6 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts @@ -25,21 +25,32 @@ const isSnapshotNameNotLowerCase = (str: string): boolean => { return strExcludeDate !== strExcludeDate.toLowerCase() ? true : false; }; +export interface ValidatePolicyHelperData { + managedRepository?: { + name: string; + policy: string; + }; + isEditing?: boolean; + policyName?: string; + /** + * Whether to block on the indices configured for this snapshot. + * + * By default ES will back up all indices and data streams if this is an empty array or left blank. + * However, in the UI, under certain conditions, like when displaying indices to select for backup, + * we want to block users from submitting an empty array, but not block the entire form if they + * are not configuring this value - like when they are on a previous step. + */ + validateIndicesCount?: boolean; +} + export const validatePolicy = ( policy: SlmPolicyPayload, - validationHelperData: { - managedRepository?: { - name: string; - policy: string; - }; - isEditing?: boolean; - policyName?: string; - } + validationHelperData: ValidatePolicyHelperData ): PolicyValidation => { const i18n = textService.i18n; const { name, snapshotName, schedule, repository, config, retention } = policy; - const { managedRepository, isEditing, policyName } = validationHelperData; + const { managedRepository, isEditing, policyName, validateIndicesCount } = validationHelperData; const validation: PolicyValidation = { isValid: true, @@ -96,7 +107,12 @@ export const validatePolicy = ( ); } - if (config && typeof config.indices === 'string' && config.indices.trim().length === 0) { + if ( + validateIndicesCount && + config && + typeof config.indices === 'string' && + config.indices.trim().length === 0 + ) { validation.errors.indices.push( i18n.translate('xpack.snapshotRestore.policyValidation.indexPatternRequiredErrorMessage', { defaultMessage: 'At least one index pattern is required.', @@ -104,7 +120,12 @@ export const validatePolicy = ( ); } - if (config && Array.isArray(config.indices) && config.indices.length === 0) { + if ( + validateIndicesCount && + config && + Array.isArray(config.indices) && + config.indices.length === 0 + ) { validation.errors.indices.push( i18n.translate('xpack.snapshotRestore.policyValidation.indicesRequiredErrorMessage', { defaultMessage: 'You must select at least one data stream or index.', From 95ac00cac9e78e8b58bf1fc01bda8df5612fa0c7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 31 Aug 2020 15:57:21 -0600 Subject: [PATCH 38/39] [Maps] fix drawing shapes in maps app broken in 7.9.1 (#76329) --- .../maps/public/routing/routes/maps_app/maps_app_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js index 6a617a0790c35..9f81fc84d7d50 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js +++ b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js @@ -459,7 +459,7 @@ export class MapsAppView extends React.Component { newFilters.forEach((filter) => { filter.$state = { store: esFilters.FilterStateStore.APP_STATE }; }); - this._onFiltersChange([...this.props.filters, ...newFilters]); + this._updateFiltersAndDispatch([...this.props.filters, ...newFilters]); }; render() { From 90a6ec8543c436f49b5fa0219f515d50f4a9d34e Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 1 Sep 2020 09:35:17 +0200 Subject: [PATCH 39/39] [7.9] Bump bl sub-dependency (#76260) (#76312) --- packages/kbn-es/package.json | 2 +- packages/kbn-test/package.json | 2 +- yarn.lock | 50 +++++----------------------------- 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 271b4a3dc661b..6cca5cbe18533 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -16,7 +16,7 @@ "glob": "^7.1.2", "node-fetch": "^2.6.0", "simple-git": "^1.91.0", - "tar-fs": "^1.16.3", + "tar-fs": "^2.1.0", "tree-kill": "^1.2.2", "yauzl": "^2.10.0" } diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index d17b53a9a423d..a2b8ebe74f410 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -34,7 +34,7 @@ "puppeteer": "^3.3.0", "rxjs": "^6.5.5", "strip-ansi": "^5.2.0", - "tar-fs": "^1.16.3", + "tar-fs": "^2.1.0", "tmp": "^0.1.0", "xml2js": "^0.4.22", "zlib": "^1.0.5" diff --git a/yarn.lock b/yarn.lock index 6f26670e4841d..d4b0ea9e580bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8733,17 +8733,10 @@ bl@^1.0.0: dependencies: readable-stream "^2.0.5" -bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== - dependencies: - readable-stream "^3.0.1" - bl@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" - integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + version "4.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" + integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== dependencies: buffer "^5.5.0" inherits "^2.0.4" @@ -25144,14 +25137,6 @@ puid@1.0.7: resolved "https://registry.yarnpkg.com/puid/-/puid-1.0.7.tgz#fa638a737d7b20419059d93965aed36ca20e1c84" integrity sha1-+mOKc317IEGQWdk5Za7TbKIOHIQ= -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -26445,7 +26430,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: +"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== @@ -29856,17 +29841,7 @@ tape@^5.0.1: string.prototype.trim "^1.2.1" through "^2.3.8" -tar-fs@^1.16.3: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== - dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" - -tar-fs@^2.0.0: +tar-fs@^2.0.0, tar-fs@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== @@ -29876,7 +29851,7 @@ tar-fs@^2.0.0: pump "^3.0.0" tar-stream "^2.0.0" -tar-stream@^1.1.2, tar-stream@^1.5.2: +tar-stream@^1.5.2: version "1.5.5" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" integrity sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg== @@ -29886,7 +29861,7 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: readable-stream "^2.0.0" xtend "^4.0.0" -tar-stream@^2.0.0: +tar-stream@^2.0.0, tar-stream@^2.1.0: version "2.1.3" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== @@ -29897,17 +29872,6 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar-stream@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== - dependencies: - bl "^3.0.0" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - tar@4.4.13, tar@^4, tar@^4.4.12: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"