From fb7b596841be2c115cd2ef78d62eeaf5d994aa78 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Thu, 24 Jun 2021 14:29:56 -0400 Subject: [PATCH 01/40] Fix missing setting modal in integrations app (#103317) --- .../public/applications/integrations/app.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index c69b6805e0e86..cad51a54d7074 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -7,7 +7,7 @@ import React, { memo, useEffect, useState } from 'react'; import type { AppMountParameters } from 'kibana/public'; -import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui'; +import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui'; import type { History } from 'history'; import { createHashHistory } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; @@ -29,10 +29,10 @@ import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; -import { AgentPolicyContextProvider } from './hooks'; +import { AgentPolicyContextProvider, useUrlModal } from './hooks'; import { INTEGRATIONS_ROUTING_PATHS } from './constants'; -import { Error, Loading } from './components'; +import { Error, Loading, SettingFlyout } from './components'; import type { UIExtensionsStorage } from './types'; @@ -234,12 +234,24 @@ export const IntegrationsAppContext: React.FC<{ ); export const AppRoutes = memo(() => { + const { modal, setModal } = useUrlModal(); return ( - - - - - - + <> + {modal === 'settings' && ( + + { + setModal(null); + }} + /> + + )} + + + + + + + ); }); From fbcf405f156ab99ab40d0de6993479712a24f55b Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:47:38 +0200 Subject: [PATCH 02/40] Add telemetry for Elastic Cloud (#102390) --- NOTICE.txt | 4 + package.json | 9 +- x-pack/plugins/cloud/public/fullstory.ts | 100 +++++++++++ .../plugins/cloud/public/plugin.test.mocks.ts | 13 ++ x-pack/plugins/cloud/public/plugin.test.ts | 164 ++++++++++++++++-- x-pack/plugins/cloud/public/plugin.ts | 79 ++++++++- .../cloud/server/assets/fullstory_library.js | 13 ++ x-pack/plugins/cloud/server/config.test.ts | 6 +- x-pack/plugins/cloud/server/plugin.ts | 8 + .../cloud/server/routes/fullstory.test.ts | 62 +++++++ .../plugins/cloud/server/routes/fullstory.ts | 75 ++++++++ x-pack/test/cloud_integration/config.ts | 87 ++++++++++ x-pack/test/cloud_integration/constants.ts | 8 + .../fixtures/saml/saml_provider/kibana.json | 7 + .../fixtures/saml/saml_provider/metadata.xml | 41 +++++ .../saml/saml_provider/server/index.ts | 15 ++ .../saml/saml_provider/server/init_routes.ts | 58 +++++++ .../saml/saml_provider/server/saml_tools.ts | 161 +++++++++++++++++ .../ftr_provider_context.d.ts | 13 ++ .../test/cloud_integration/tests/fullstory.ts | 66 +++++++ yarn.lock | 5 + 21 files changed, 973 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/cloud/public/fullstory.ts create mode 100644 x-pack/plugins/cloud/public/plugin.test.mocks.ts create mode 100644 x-pack/plugins/cloud/server/assets/fullstory_library.js create mode 100644 x-pack/plugins/cloud/server/routes/fullstory.test.ts create mode 100644 x-pack/plugins/cloud/server/routes/fullstory.ts create mode 100644 x-pack/test/cloud_integration/config.ts create mode 100644 x-pack/test/cloud_integration/constants.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/kibana.json create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/metadata.xml create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/index.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/init_routes.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/saml_tools.ts create mode 100644 x-pack/test/cloud_integration/ftr_provider_context.d.ts create mode 100644 x-pack/test/cloud_integration/tests/fullstory.ts diff --git a/NOTICE.txt b/NOTICE.txt index 4eec329b7a603..b0f7e65f46fa9 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -235,6 +235,10 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- +Portions of this code are licensed under the following license: +For license information please see https://edge.fullstory.com/s/fs.js.LICENSE.txt + --- This product bundles bootstrap@3.3.6 which is available under a "MIT" license. diff --git a/package.json b/package.json index ecedb64c343ec..ceb178d068519 100644 --- a/package.json +++ b/package.json @@ -128,25 +128,26 @@ "@kbn/analytics": "link:bazel-bin/packages/kbn-analytics", "@kbn/apm-config-loader": "link:bazel-bin/packages/kbn-apm-config-loader", "@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils", + "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/config": "link:bazel-bin/packages/kbn-config", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema", "@kbn/crypto": "link:bazel-bin/packages/kbn-crypto", - "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n", "@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter", "@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils", "@kbn/legacy-logging": "link:bazel-bin/packages/kbn-legacy-logging", "@kbn/logging": "link:bazel-bin/packages/kbn-logging", + "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/monaco": "link:bazel-bin/packages/kbn-monaco", "@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils", - "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils", "@kbn/securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils", - "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types", "@kbn/securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types", "@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types", + "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types", "@kbn/securitysolution-io-ts-utils": "link:bazel-bin/packages/kbn-securitysolution-io-ts-utils", "@kbn/securitysolution-list-api": "link:bazel-bin/packages/kbn-securitysolution-list-api", + "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-list-hooks": "link:bazel-bin/packages/kbn-securitysolution-list-hooks", "@kbn/securitysolution-list-utils": "link:bazel-bin/packages/kbn-securitysolution-list-utils", "@kbn/securitysolution-t-grid": "link:bazel-bin/packages/kbn-securitysolution-t-grid", @@ -158,7 +159,6 @@ "@kbn/ui-framework": "link:bazel-bin/packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", - "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/utils": "link:bazel-bin/packages/kbn-utils", "@loaders.gl/core": "^2.3.1", "@loaders.gl/json": "^2.3.1", @@ -273,6 +273,7 @@ "jquery": "^3.5.0", "js-levenshtein": "^1.1.6", "js-search": "^1.4.3", + "js-sha256": "^0.9.0", "js-yaml": "^3.14.0", "json-stable-stringify": "^1.0.1", "json-stringify-pretty-compact": "1.2.0", diff --git a/x-pack/plugins/cloud/public/fullstory.ts b/x-pack/plugins/cloud/public/fullstory.ts new file mode 100644 index 0000000000000..31e5ec128b9a3 --- /dev/null +++ b/x-pack/plugins/cloud/public/fullstory.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sha256 } from 'js-sha256'; +import type { IBasePath, PackageInfo } from '../../../../src/core/public'; + +export interface FullStoryDeps { + basePath: IBasePath; + orgId: string; + packageInfo: PackageInfo; + userIdPromise: Promise; +} + +interface FullStoryApi { + identify(userId: string, userVars?: Record): void; + event(eventName: string, eventProperties: Record): void; +} + +export const initializeFullStory = async ({ + basePath, + orgId, + packageInfo, + userIdPromise, +}: FullStoryDeps) => { + // @ts-expect-error + window._fs_debug = false; + // @ts-expect-error + window._fs_host = 'fullstory.com'; + // @ts-expect-error + window._fs_script = basePath.prepend(`/internal/cloud/${packageInfo.buildNum}/fullstory.js`); + // @ts-expect-error + window._fs_org = orgId; + // @ts-expect-error + window._fs_namespace = 'FSKibana'; + + /* eslint-disable */ + (function(m,n,e,t,l,o,g,y){ + if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;} + // @ts-expect-error + g=m[e]=function(a,b,s){g.q?g.q.push([a,b,s]):g._api(a,b,s);};g.q=[]; + // @ts-expect-error + o=n.createElement(t);o.async=1;o.crossOrigin='anonymous';o.src=_fs_script; + // @ts-expect-error + y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y); + // @ts-expect-error + g.identify=function(i,v,s){g(l,{uid:i},s);if(v)g(l,v,s)};g.setUserVars=function(v,s){g(l,v,s)};g.event=function(i,v,s){g('event',{n:i,p:v},s)}; + // @ts-expect-error + g.anonymize=function(){g.identify(!!0)}; + // @ts-expect-error + g.shutdown=function(){g("rec",!1)};g.restart=function(){g("rec",!0)}; + // @ts-expect-error + g.log = function(a,b){g("log",[a,b])}; + // @ts-expect-error + g.consent=function(a){g("consent",!arguments.length||a)}; + // @ts-expect-error + g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)}; + // @ts-expect-error + g.clearUserCookie=function(){}; + // @ts-expect-error + g.setVars=function(n, p){g('setVars',[n,p]);}; + // @ts-expect-error + g._w={};y='XMLHttpRequest';g._w[y]=m[y];y='fetch';g._w[y]=m[y]; + // @ts-expect-error + if(m[y])m[y]=function(){return g._w[y].apply(this,arguments)}; + // @ts-expect-error + g._v="1.3.0"; + // @ts-expect-error + })(window,document,window['_fs_namespace'],'script','user'); + /* eslint-enable */ + + // @ts-expect-error + const fullstory: FullStoryApi = window.FSKibana; + + // Record an event that Kibana was opened so we can easily search for sessions that use Kibana + // @ts-expect-error + window.FSKibana.event('Loaded Kibana', { + kibana_version_str: packageInfo.version, + }); + + // Use a promise here so we don't have to wait to retrieve the user to start recording the session + userIdPromise + .then((userId) => { + if (!userId) return; + // Do the hashing here to keep it at clear as possible in our source code that we do not send literal user IDs + const hashedId = sha256(userId.toString()); + // @ts-expect-error + window.FSKibana.identify(hashedId); + }) + .catch((e) => { + // eslint-disable-next-line no-console + console.error( + `[cloud.full_story] Could not call FS.identify due to error: ${e.toString()}`, + e + ); + }); +}; diff --git a/x-pack/plugins/cloud/public/plugin.test.mocks.ts b/x-pack/plugins/cloud/public/plugin.test.mocks.ts new file mode 100644 index 0000000000000..889b8492d5b1b --- /dev/null +++ b/x-pack/plugins/cloud/public/plugin.test.mocks.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FullStoryDeps } from './fullstory'; + +export const initializeFullStoryMock = jest.fn(); +jest.doMock('./fullstory', () => { + return { initializeFullStory: initializeFullStoryMock }; +}); diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 981c5138d98d2..af4d3c4c9005d 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -9,11 +9,98 @@ import { nextTick } from '@kbn/test/jest'; import { coreMock } from 'src/core/public/mocks'; import { homePluginMock } from 'src/plugins/home/public/mocks'; import { securityMock } from '../../security/public/mocks'; -import { CloudPlugin } from './plugin'; +import { initializeFullStoryMock } from './plugin.test.mocks'; +import { CloudPlugin, CloudConfigType, loadFullStoryUserId } from './plugin'; describe('Cloud Plugin', () => { + describe('#setup', () => { + describe('setupFullstory', () => { + beforeEach(() => { + initializeFullStoryMock.mockReset(); + }); + + const setupPlugin = async ({ + config = {}, + securityEnabled = true, + currentUserProps = {}, + }: { + config?: Partial; + securityEnabled?: boolean; + currentUserProps?: Record; + }) => { + const initContext = coreMock.createPluginInitializerContext({ + id: 'cloudId', + base_url: 'https://cloud.elastic.co', + deployment_url: '/abc123', + profile_url: '/profile/alice', + organization_url: '/org/myOrg', + full_story: { + enabled: false, + }, + ...config, + }); + const plugin = new CloudPlugin(initContext); + + const coreSetup = coreMock.createSetup(); + const securitySetup = securityMock.createSetup(); + securitySetup.authc.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser(currentUserProps) + ); + + const setup = plugin.setup(coreSetup, securityEnabled ? { security: securitySetup } : {}); + // Wait for fullstory dynamic import to resolve + await new Promise((r) => setImmediate(r)); + + return { initContext, plugin, setup }; + }; + + it('calls initializeFullStory with correct args when enabled and org_id are set', async () => { + const { initContext } = await setupPlugin({ + config: { full_story: { enabled: true, org_id: 'foo' } }, + currentUserProps: { + username: '1234', + }, + }); + + expect(initializeFullStoryMock).toHaveBeenCalled(); + const { + basePath, + orgId, + packageInfo, + userIdPromise, + } = initializeFullStoryMock.mock.calls[0][0]; + expect(basePath.prepend).toBeDefined(); + expect(orgId).toEqual('foo'); + expect(packageInfo).toEqual(initContext.env.packageInfo); + expect(await userIdPromise).toEqual('1234'); + }); + + it('passes undefined user ID when security is not available', async () => { + await setupPlugin({ + config: { full_story: { enabled: true, org_id: 'foo' } }, + securityEnabled: false, + }); + + expect(initializeFullStoryMock).toHaveBeenCalled(); + const { orgId, userIdPromise } = initializeFullStoryMock.mock.calls[0][0]; + expect(orgId).toEqual('foo'); + expect(await userIdPromise).toEqual(undefined); + }); + + it('does not call initializeFullStory when enabled=false', async () => { + await setupPlugin({ config: { full_story: { enabled: false, org_id: 'foo' } } }); + expect(initializeFullStoryMock).not.toHaveBeenCalled(); + }); + + it('does not call initializeFullStory when org_id is undefined', async () => { + await setupPlugin({ config: { full_story: { enabled: true } } }); + expect(initializeFullStoryMock).not.toHaveBeenCalled(); + }); + }); + }); + describe('#start', () => { - function setupPlugin() { + const startPlugin = () => { const plugin = new CloudPlugin( coreMock.createPluginInitializerContext({ id: 'cloudId', @@ -21,6 +108,9 @@ describe('Cloud Plugin', () => { deployment_url: '/abc123', profile_url: '/profile/alice', organization_url: '/org/myOrg', + full_story: { + enabled: false, + }, }) ); const coreSetup = coreMock.createSetup(); @@ -29,10 +119,10 @@ describe('Cloud Plugin', () => { plugin.setup(coreSetup, { home: homeSetup }); return { coreSetup, plugin }; - } + }; it('registers help support URL', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -47,7 +137,7 @@ describe('Cloud Plugin', () => { }); it('does not register custom nav links on anonymous pages', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); @@ -68,7 +158,7 @@ describe('Cloud Plugin', () => { }); it('registers a custom nav link for superusers', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -94,7 +184,7 @@ describe('Cloud Plugin', () => { }); it('registers a custom nav link when there is an error retrieving the current user', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -116,7 +206,7 @@ describe('Cloud Plugin', () => { }); it('does not register a custom nav link for non-superusers', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -133,7 +223,7 @@ describe('Cloud Plugin', () => { }); it('registers user profile links for superusers', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -169,7 +259,7 @@ describe('Cloud Plugin', () => { }); it('registers profile links when there is an error retrieving the current user', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -201,7 +291,7 @@ describe('Cloud Plugin', () => { }); it('does not register profile links for non-superusers', async () => { - const { plugin } = setupPlugin(); + const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); const securityStart = securityMock.createStart(); @@ -217,4 +307,56 @@ describe('Cloud Plugin', () => { expect(securityStart.navControlService.addUserMenuLinks).not.toHaveBeenCalled(); }); }); + + describe('loadFullStoryUserId', () => { + let consoleMock: jest.SpyInstance; + + beforeEach(() => { + consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {}); + }); + afterEach(() => { + consoleMock.mockRestore(); + }); + + it('returns principal ID when username specified', async () => { + expect( + await loadFullStoryUserId({ + getCurrentUser: jest.fn().mockResolvedValue({ + username: '1234', + }), + }) + ).toEqual('1234'); + expect(consoleMock).not.toHaveBeenCalled(); + }); + + it('returns undefined if getCurrentUser throws', async () => { + expect( + await loadFullStoryUserId({ + getCurrentUser: jest.fn().mockRejectedValue(new Error(`Oh no!`)), + }) + ).toBeUndefined(); + }); + + it('returns undefined if getCurrentUser returns undefined', async () => { + expect( + await loadFullStoryUserId({ + getCurrentUser: jest.fn().mockResolvedValue(undefined), + }) + ).toBeUndefined(); + }); + + it('returns undefined and logs if username undefined', async () => { + expect( + await loadFullStoryUserId({ + getCurrentUser: jest.fn().mockResolvedValue({ + username: undefined, + metadata: { foo: 'bar' }, + }), + }) + ).toBeUndefined(); + expect(consoleMock).toHaveBeenLastCalledWith( + `[cloud.full_story] username not specified. User metadata: {"foo":"bar"}` + ); + }); + }); }); diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 5820452706539..68dece1bc5d3d 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -5,9 +5,20 @@ * 2.0. */ -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext, HttpStart } from 'src/core/public'; +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + HttpStart, + IBasePath, +} from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { SecurityPluginSetup, SecurityPluginStart } from '../../security/public'; +import type { + AuthenticatedUser, + SecurityPluginSetup, + SecurityPluginStart, +} from '../../security/public'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { ELASTIC_SUPPORT_LINK } from '../common/constants'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; @@ -21,6 +32,10 @@ export interface CloudConfigType { profile_url?: string; deployment_url?: string; organization_url?: string; + full_story: { + enabled: boolean; + org_id?: string; + }; } interface CloudSetupDependencies { @@ -51,7 +66,12 @@ export class CloudPlugin implements Plugin { this.isCloudEnabled = false; } - public setup(core: CoreSetup, { home }: CloudSetupDependencies) { + public setup(core: CoreSetup, { home, security }: CloudSetupDependencies) { + this.setupFullstory({ basePath: core.http.basePath, security }).catch((e) => + // eslint-disable-next-line no-console + console.debug(`Error setting up FullStory: ${e.toString()}`) + ); + const { id, cname, @@ -135,4 +155,57 @@ export class CloudPlugin implements Plugin { const user = await security.authc.getCurrentUser().catch(() => null); return user?.roles.includes('superuser') ?? true; } + + private async setupFullstory({ + basePath, + security, + }: CloudSetupDependencies & { basePath: IBasePath }) { + const { enabled, org_id: orgId } = this.config.full_story; + if (!enabled || !orgId) { + return; + } + + // Keep this import async so that we do not load any FullStory code into the browser when it is disabled. + const { initializeFullStory } = await import('./fullstory'); + const userIdPromise: Promise = security + ? loadFullStoryUserId({ getCurrentUser: security.authc.getCurrentUser }) + : Promise.resolve(undefined); + + initializeFullStory({ + basePath, + orgId, + packageInfo: this.initializerContext.env.packageInfo, + userIdPromise, + }); + } } + +/** @internal exported for testing */ +export const loadFullStoryUserId = async ({ + getCurrentUser, +}: { + getCurrentUser: () => Promise; +}) => { + try { + const currentUser = await getCurrentUser().catch(() => undefined); + if (!currentUser) { + return undefined; + } + + // Log very defensively here so we can debug this easily if it breaks + if (!currentUser.username) { + // eslint-disable-next-line no-console + console.debug( + `[cloud.full_story] username not specified. User metadata: ${JSON.stringify( + currentUser.metadata + )}` + ); + } + + return currentUser.username; + } catch (e) { + // eslint-disable-next-line no-console + console.error(`[cloud.full_story] Error loading the current user: ${e.toString()}`, e); + return undefined; + } +}; diff --git a/x-pack/plugins/cloud/server/assets/fullstory_library.js b/x-pack/plugins/cloud/server/assets/fullstory_library.js new file mode 100644 index 0000000000000..487d6c8e8528c --- /dev/null +++ b/x-pack/plugins/cloud/server/assets/fullstory_library.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* @notice + * Portions of this code are licensed under the following license: + * For license information please see https://edge.fullstory.com/s/fs.js.LICENSE.txt + */ +/* eslint-disable */ +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/s",n(n.s=4)}([function(e,t,n){"use strict";var r=this&&this.__assign||function(){return(r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0},t.assertExhaustive=function(e,t){throw void 0===t&&(t="Reached unexpected case in exhaustive switch"),new Error(t)},t.pick=function(e){for(var t=[],n=1;na&&(i=a);var u=n.split(/[#,]/);if(u.length<3&&(u=n.split("`")).length<3)return null;var c=u[0],h=u[1],d=u[2],l=u[3],p="";void 0!==l&&(p=decodeURIComponent(l),(f.indexOf(p)>=0||v.indexOf(p)>=0)&&(o("Ignoring invalid app key \""+p+"\" from cookie."),p=""));var m=d.split(":");return{expirationAbsTimeSeconds:i,host:c,orgId:h,userId:m[0],sessionId:m[1]||"",appKeyHash:p}}function y(e){for(var t={},n=e.cookie.split(";"),r=0;r=0&&(t=t.slice(0,n)),t}(e))?e:0==e.indexOf("www.")?"app."+e.slice(4):"app."+e:e}function H(e){return e?e+"/s/fs.js":void 0}!function(e){e.MUT_INSERT=2,e.MUT_REMOVE=3,e.MUT_ATTR=4,e.MUT_TEXT=6,e.MOUSEMOVE=8,e.MOUSEMOVE_CURVE=9,e.SCROLL_LAYOUT=10,e.SCROLL_LAYOUT_CURVE=11,e.MOUSEDOWN=12,e.MOUSEUP=13,e.KEYDOWN=14,e.KEYUP=15,e.CLICK=16,e.FOCUS=17,e.VALUECHANGE=18,e.RESIZE_LAYOUT=19,e.DOMLOADED=20,e.LOAD=21,e.PLACEHOLDER_SIZE=22,e.UNLOAD=23,e.BLUR=24,e.SET_FRAME_BASE=25,e.TOUCHSTART=32,e.TOUCHEND=33,e.TOUCHCANCEL=34,e.TOUCHMOVE=35,e.TOUCHMOVE_CURVE=36,e.NAVIGATE=37,e.PLAY=38,e.PAUSE=39,e.RESIZE_VISUAL=40,e.RESIZE_VISUAL_CURVE=41,e.RESIZE_DOCUMENT=42,e.LOG=48,e.ERROR=49,e.DBL_CLICK=50,e.FORM_SUBMIT=51,e.WINDOW_FOCUS=52,e.WINDOW_BLUR=53,e.HEARTBEAT=54,e.WATCHED_ELEM=56,e.PERF_ENTRY=57,e.REC_FEAT_SUPPORTED=58,e.SELECT=59,e.CSSRULE_INSERT=60,e.CSSRULE_DELETE=61,e.FAIL_THROTTLED=62,e.AJAX_REQUEST=63,e.SCROLL_VISUAL_OFFSET=64,e.SCROLL_VISUAL_OFFSET_CURVE=65,e.MEDIA_QUERY_CHANGE=66,e.RESOURCE_TIMING_BUFFER_FULL=67,e.MUT_SHADOW=68,e.DISABLE_STYLESHEET=69,e.FULLSCREEN=70,e.FULLSCREEN_ERROR=71,e.ADOPTED_STYLESHEETS=72,e.CUSTOM_ELEMENT_DEFINED=73,e.MODAL_OPEN=74,e.MODAL_CLOSE=75,e.SLOW_INTERACTION=76,e.LONG_FRAME=77,e.TIMING=78,e.STORAGE_WRITE_FAILURE=79,e.KEEP_ELEMENT=2e3,e.KEEP_URL=2001,e.KEEP_BOUNCE=2002,e.SYS_SETVAR=8193,e.SYS_RESOURCEHASH=8195,e.SYS_SETCONSENT=8196,e.SYS_CUSTOM=8197,e.SYS_REPORTCONSENT=8198}(R||(R={})),function(e){e.Unknown=0,e.Serialization=1}(A||(A={})),function(e){e.Unknown=0,e.DomSnapshot=1,e.NodeEncoding=2,e.LzEncoding=3}(x||(x={})),function(e){e.Internal=0,e.Public=1}(O||(O={}));var j,K,V,z,Y,G,Q,X,J,$,Z,ee,te,ne=["print","alert","confirm"];function re(e){switch(e){case R.MOUSEDOWN:case R.MOUSEMOVE:case R.MOUSEMOVE_CURVE:case R.MOUSEUP:case R.KEYDOWN:case R.KEYUP:case R.TOUCHSTART:case R.TOUCHEND:case R.TOUCHMOVE:case R.TOUCHMOVE_CURVE:case R.TOUCHCANCEL:case R.CLICK:case R.SCROLL_LAYOUT:case R.SCROLL_LAYOUT_CURVE:case R.SCROLL_VISUAL_OFFSET:case R.SCROLL_VISUAL_OFFSET_CURVE:case R.NAVIGATE:return!0;}return!1}!function(e){e.GrantConsent=!0,e.RevokeConsent=!1}(j||(j={})),function(e){e.Page=0,e.Document=1}(K||(K={})),function(e){e.Unknown=0,e.Api=1,e.FsShutdownFrame=2,e.Hibernation=3,e.Reidentify=4,e.SettingsBlocked=5,e.Size=6,e.Unload=7}(V||(V={})),function(e){e.Timing=0,e.Navigation=1,e.Resource=2,e.Paint=3,e.Mark=4,e.Measure=5,e.Memory=6}(z||(z={})),function(e){e.Performance=0,e.PerformanceEntries=1,e.PerformanceMemory=2,e.Console=3,e.Ajax=4,e.PerformanceObserver=5,e.AjaxFetch=6}(Y||(Y={})),function(e){e.Node=1,e.Sheet=2}(G||(G={})),function(e){e.StyleSheetHooks=0,e.SetPropertyHooks=1}(Q||(Q={})),function(e){e.User="user",e.Account="acct",e.Event="evt"}(X||(X={})),function(e){e.Elide=0,e.Record=1,e.Whitelist=2}(J||(J={})),function(e){e.ReasonNoSuchOrg=1,e.ReasonOrgDisabled=2,e.ReasonOrgOverQuota=3,e.ReasonBlockedDomain=4,e.ReasonBlockedIp=5,e.ReasonBlockedUserAgent=6,e.ReasonBlockedGeo=7,e.ReasonBlockedTrafficRamping=8,e.ReasonInvalidURL=9,e.ReasonUserOptOut=10,e.ReasonInvalidRecScript=11,e.ReasonDeletingUser=12,e.ReasonNativeHookFailure=13}($||($={})),function(e){e.Unset=0,e.Exclude=1,e.Mask=2,e.Unmask=3,e.Watch=4,e.Keep=5}(Z||(Z={})),function(e){e.Unset=0,e.Click=1}(ee||(ee={})),function(e){e.MaxLogsPerPage=1024,e.MutationProcessingInterval=250,e.CurveSamplingInterval=142,e.DefaultBundleUploadInterval=5e3,e.HeartbeatInitial=4e3,e.HeartbeatMax=256200,e.PageInactivityTimeout=18e5,e.BackoffMax=3e5,e.ScrollSampleInterval=e.MutationProcessingInterval/5,e.InactivityThreshold=4e3,e.MaxPayloadLength=16384}(te||(te={}));function ie(e,t){return function(){try{return e.apply(this,arguments)}catch(e){try{t&&t(e)}catch(e){}}}}var oe=function(){},se=navigator.userAgent,ae=se.indexOf("MSIE ")>-1||se.indexOf("Trident/")>-1,ue=(ae&&se.indexOf("Trident/5"),ae&&se.indexOf("Trident/6"),ae&&se.indexOf("rv:11")>-1),ce=se.indexOf("Edge/")>-1;se.indexOf("CriOS");var he=/^((?!chrome|android).)*safari/i.test(window.navigator.userAgent);function de(){var e=window.navigator.userAgent.match(/Version\/(\d+)/);return e?parseInt(e[1]):-1}function le(e){if(!he)return!1;var t=de();return t>=0&&t===e}function pe(e){if(!he)return!1;var t=de();return t>=0&&tt)return!1;return n==t}function pt(e,t){var n=0;for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)&&++n>t)return!0;return!1}Ze="function"==typeof s.objectKeys?function(e){return s.objectKeys(e)}:function(e){var t=[];for(var n in e)s.objectHasOwnProp(e,n)&&t.push(n);return t},st=(ot=function(e){return e.matches||e.msMatchesSelector||e.webkitMatchesSelector})(window.Element.prototype),!(at=window.document?window.document.documentElement:void 0)||st&&at instanceof window.Element||(st=ot(at)),it=($e=[st,function(e,t){return st.call(e,t)}])[0],rt=$e[1];var ft;ut=ae?function(e){var t=e.nextSibling;return t&&e.parentNode&&t===e.parentNode.firstChild?null:t}:function(e){return e.nextSibling};var vt;ft=ae?function(e,t){if(e){var n=e.parentNode?e.parentNode.firstChild:null;do{t(e),e=e.nextSibling}while(e&&e!=n)}}:function(e,t){for(;e;e=e.nextSibling)t(e)};vt=ae?function(e){var t=e.previousSibling;return t&&e.parentNode&&t===e.parentNode.lastChild?null:t}:function(e){return e.previousSibling};function _t(e,t){if(!e)return oe;var n=function(e){try{var t=window;return t.Zone&&t.Zone.root&&"function"==typeof t.Zone.root.wrap?t.Zone.root.wrap(e):e}catch(t){return e}}(e);return t&&(n=n.bind(t)),ie(n,function(e){o("Unexpected error: "+e)})}function gt(e){var t,n=Array.prototype.toJSON,r=String.prototype.toJSON;n&&(Array.prototype.toJSON=void 0),r&&(String.prototype.toJSON=void 0);try{t=s.jsonStringify(e)}catch(e){t=mt(e)}finally{n&&(Array.prototype.toJSON=n),r&&(String.prototype.toJSON=r)}return t}function mt(e){var t="Internal error: unable to determine what JSON error was";try{t=(t=""+e).replace(/[^a-zA-Z0-9\.\:\!\, ]/g,"_")}catch(e){}return"\""+t+"\""}function yt(e){var t=e.doctype;if(!t)return"";var n=""}function wt(e){return s.jsonParse(e)}function bt(e){var t=0,n=0;return null==e.screen?[t,n]:(t=parseInt(String(e.screen.width)),n=parseInt(String(e.screen.height)),[t=isNaN(t)?0:t,n=isNaN(n)?0:n])}var St=function(){function e(e,t){this.target=e,this.propertyName=t,this._before=oe,this._afterSync=oe,this._afterAsync=oe,this.on=!1}return e.prototype.before=function(e){return this._before=_t(e),this},e.prototype.afterSync=function(e){return this._afterSync=_t(e),this},e.prototype.afterAsync=function(e){return this._afterAsync=_t(function(t){s.setWindowTimeout(window,ie(function(){e(t)}),0)}),this},e.prototype.disable=function(){if(this.on=!1,this.shim){var e=this.shim,t=e.override,n=e["native"];this.target[this.propertyName]===t&&(this.target[this.propertyName]=n,this.shim=void 0)}},e.prototype.enable=function(){if(this.on=!0,this.shim)return!0;this.shim=this.makeShim();try{this.target[this.propertyName]=this.shim.override}catch(e){return!1}return!0},e.prototype.makeShim=function(){var e=this,t=this.target[this.propertyName];return{"native":t,override:function(){var n={that:this,args:arguments,result:null};e.on&&e._before(n);var r=t.apply(this,arguments);return e.on&&(n.result=r,e._afterSync(n),e._afterAsync(n)),r}}},e}(),Et={};function Tt(e,t){if(!e||"function"!=typeof e[t])return null;var n;Et[t]=Et[t]||[];for(var r=0;r\n";var n=[];try{for(var r=arguments.callee.caller.caller;r&&n.lengthn)return!1;var r=new Error("Assertion failed: "+t);return Mt.sendToBugsnag(r,"error"),e}function Pt(e,t,n,r){void 0!==n&&("function"==typeof e.addEventListener?e.addEventListener(t,n,r):"function"==typeof e.addListener?e.addListener(n):o("Target of "+t+" doesn't seem to support listeners"))}function qt(e,t,n,r){void 0!==n&&("function"==typeof e.removeEventListener?e.removeEventListener(t,n,r):"function"==typeof e.removeListener?e.removeListener(n):o("Target of "+t+" doesn't seem to support listeners"))}var Ut=function(){function e(){var e=this;this._listeners=[],this._children=[],this._yesCapture=!0,this._noCapture=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e._yesCapture={capture:!0,passive:!0},e._noCapture={capture:!1,passive:!0}}});window.addEventListener("test",oe,t)}catch(e){}}return e.prototype.add=function(e,t,n,r,i){return void 0===i&&(i=!1),this.addCustom(e,t,n,r,i)},e.prototype.addCustom=function(e,t,n,r,i){void 0===i&&(i=!1);var o={target:e,type:t,fn:Mt.wrap(function(e){(i||!1!==e.isTrusted||"message"==t||e._fs_trust_event)&&r(e)}),options:n?this._yesCapture:this._noCapture,index:this._listeners.length};return this._listeners.push(o),Pt(e,t,o.fn,o.options),o},e.prototype.remove=function(e){e.target&&(qt(e.target,e.type,e.fn,e.options),e.target=null,e.fn=void 0)},e.prototype.clear=function(){for(var e=0;e0&&t.height>0)return this.width=t.width,void(this.height=t.height);r=this.computeLayoutViewportSizeFromMediaQueries(e),this.width=r[0],this.height=r[1]}}return e.prototype.computeLayoutViewportSizeFromMediaQueries=function(e){var t=this.findMediaValue(e,"width",this.clientWidth,this.clientWidth+128);void 0===t&&(t=this.tryToGet(e,"innerWidth")),void 0===t&&(t=this.clientWidth);var n=this.findMediaValue(e,"height",this.clientHeight,this.clientHeight+128);return void 0===n&&(n=this.tryToGet(e,"innerHeight")),void 0===n&&(n=this.clientHeight),[t,n]},e.prototype.findMediaValue=function(e,t,n,r){if(s.matchMedia){var i=s.matchMedia(e,"(min-"+t+": "+n+"px)");if(null!=i){if(i.matches&&s.matchMedia(e,"(max-"+t+": "+n+"px)").matches)return n;for(;n<=r;){var o=s.mathFloor((n+r)/2);if(s.matchMedia(e,"(min-"+t+": "+o+"px)").matches){if(s.matchMedia(e,"(max-"+t+": "+o+"px)").matches)return o;n=o+1}else r=o-1}}}},e.prototype.tryToGet=function(e,t){try{return e[t]}catch(e){return}},e}();function Yt(e,t){return new zt(e,t)}var Gt=function(e,t){this.offsetLeft=0,this.offsetTop=0,this.pageLeft=0,this.pageTop=0,this.width=0,this.height=0,this.scale=0;var n=e.document;if(n.body){var r="BackCompat"==n.compatMode;"pageXOffset"in e?(this.pageLeft=e.pageXOffset,this.pageTop=e.pageYOffset):n.scrollingElement?(this.pageLeft=n.scrollingElement.scrollLeft,this.pageTop=n.scrollingElement.scrollTop):r?(this.pageLeft=n.body.scrollLeft,this.pageTop=n.body.scrollTop):n.documentElement&&(n.documentElement.scrollLeft>0||n.documentElement.scrollTop>0)?(this.pageLeft=n.documentElement.scrollLeft,this.pageTop=n.documentElement.scrollTop):(this.pageLeft=n.body.scrollLeft||0,this.pageTop=n.body.scrollTop||0),this.offsetLeft=this.pageLeft-t.pageLeft,this.offsetTop=this.pageTop-t.pageTop;try{var i=e.innerWidth,o=e.innerHeight}catch(e){return}if(0!=i&&0!=o){this.scale=t.width/i,this.scale<1&&(this.scale=1);var s=t.width-t.clientWidth,a=t.height-t.clientHeight;this.width=i-s/this.scale,this.height=o-a/this.scale}}};var Qt,Xt=(Qt=function(e,t){return(Qt=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}Qt(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),Jt=function(e,t,n,r){return new(n||(n=Promise))(function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r["throw"](e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,a)}u((r=r.apply(e,t||[])).next())})},$t=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),"throw":a(1),"return":a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r["return"]:o[0]?r["throw"]||((i=r["return"])&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]this._due)return et.resolve().then(this._wrappedTick)["catch"](function(){})},e.registry={},e.nextId=0,e.checkedAlready=!1,e.lastCheck=0,e}(),en=function(e){function t(t){var n=e.call(this)||this;return n._interval=t,n._handle=-1,n}return Xt(t,e),t.prototype.start=function(e){var t=this;-1==this._handle&&(this.setTick(function(){e(),t.register(t._interval)}),this._handle=s.setWindowInterval(window,this._wrappedTick,this._interval),this.register(this._interval))},t.prototype.cancel=function(){-1!=this._handle&&(s.clearWindowInterval(window,this._handle),this._handle=-1,this.setTick(function(){}))},t}(Zt),tn=function(e){function t(t,n,r){void 0===n&&(n=0);for(var i=[],o=3;ot&&(this._skew=e-t,this._reportTimeSkew("timekeeper set with future ts"))},e.prototype._reportTimeSkew=function(e){this._reported++<=2&&Mt.sendToBugsnag(e,"error",{skew:this._skew,startTime:this._startTime,wallTime:this.wallTime()})},e}();function on(e){var t=e;return t.tagName?"object"==typeof t.tagName?"form":t.tagName.toLowerCase():null}var sn,an,un=n(3),cn=n(0),hn=Object.defineProperty,dn=p()%1e9,ln=window.WeakMap||((sn=function(){this.name="__st"+(1e9*s.mathRandom()>>>0)+dn++ +"__"}).prototype={set:function(e,t){var n=e[this.name];n&&n[0]===e?n[1]=t:hn(e,this.name,{value:[e,t],writable:!0})},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0}},sn),pn=1,fn=4,vn=function(){for(var e=0,t=0,n=arguments.length;t0&&o(n[u],this._rules[u]),r[u].length>0&&o(r[u],this._consentRules[u])}},e.prototype._fallback=function(e){for(var t=0,n=e;t0&&t.length<1e4;){var n=t.pop();delete Tn[n.id],n.node._fs==n.id&&(n.node._fs=0),n.id=0,n.next&&t.push(n.next),n.child&&t.push(n.child)}Ft(t.length<1e4,"clearIds is fast")}var qn,Un=function(){function e(e,t){this._onchange=e,this._checkElem=t,this._fallback=!1,this._elems={},this.values={},this.radios={},qn=this}return e.prototype.hookEvents=function(){(function(){var e=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"value");if(!e||!e.set)return!1;Nn||(kt(HTMLInputElement,"value",jn),kt(HTMLInputElement,"checked",jn),kt(HTMLSelectElement,"value",jn),kt(HTMLTextAreaElement,"value",jn),kt(HTMLSelectElement,"selectedIndex",jn),kt(HTMLOptionElement,"selected",jn),Nn=!0);return!0})()||(this._fallback=!0)},e.prototype.addInput=function(e){var t=Mn(e);if(this._elems[t]=e,Vn(e)){var n=Hn(e);e.checked&&(this.radios[n]=t)}else this.values[t]=Kn(e);(function(e){switch(e.type){case"checkbox":case"radio":return e.checked!=e.hasAttribute("checked");default:return(e.value||"")!=function(e){if("select"!=on(e))return e.getAttribute("value")||"";var t=e,n=t.querySelector("option[selected]")||t.querySelector("option");if(!n)return"";return n.value||""}(e);}})(e)&&this._onchange(e)},e.prototype.diffValue=function(e,t){var n=Mn(e);if(Vn(e)){var r=Hn(e);return this.radios[r]==n!=("true"==t)}return this.values[n]!=t},e.prototype.onChange=function(e,t){void 0===t&&(t=Kn(e));var n=Mn(e);if((e=this._elems[n])&&this.diffValue(e,t))if(this._onchange(e),Vn(e)){var r=Hn(e);"false"==t&&this.radios[r]==n?delete this.radios[r]:this.radios[r]=n}else this.values[n]=t},e.prototype.tick=function(){for(var e in this._elems){var t=this._elems[e];if(this._checkElem(t)){if(this._fallback){var n=Kn(t);if(t&&this.diffValue(t,n))if(this._onchange(t),Vn(t)){var r=Hn(t);this.radios[r]=+e}else this.values[e]=n}}else delete this._elems[e],delete this.values[e],Vn(t)&&delete this.radios[Hn(t)]}},e.prototype.shutdown=function(){qn=null},e.prototype._usingFallback=function(){return this._fallback},e.prototype._trackingElem=function(e){return!!this._elems[e]},e}(),Nn=!1;var Wn,Dn={};function Bn(){try{if(qn)for(var e in Dn){var t=Dn[e],n=t[0],r=t[1];qn.onChange(n,r)}}finally{Wn=null,Dn={}}}function Hn(e){if(!e)return"";for(var t=e;t&&"form"!=on(t);)t=t.parentElement;return(t&&"form"==on(t)?Mn(t):0)+":"+e.name}function jn(e,t){var n=function e(t,n){if(void 0===n&&(n=2),n<=0)return t;var r=on(t);return"option"!=r&&"optgroup"!=r||!t.parentElement?t:e(t.parentElement,n-1)}(e),r=Mn(n);r&&qn&&qn.diffValue(n,""+t)&&(Dn[r]=[n,""+t],Wn||(Wn=new tn(Bn)).start())}function Kn(e){switch(e.type){case"checkbox":case"radio":return""+e.checked;default:var t=e.value;return t||(t=""),""+t;}}function Vn(e){return e&&"radio"==e.type}var zn={};var Yn="__default";function Gn(e){void 0===e&&(e=Yn);var t=zn[e];return t||(t=function(){var e=document.implementation.createHTMLDocument("");return e.head||e.documentElement.appendChild(e.createElement("head")),e.body||e.documentElement.appendChild(e.createElement("body")),e}(),e!==Yn&&(t.open(),t.write(e),t.close()),zn[e]=t),t}var Qn=new(function(){function e(){var e=Gn(),t=e.getElementById("urlresolver-base");t||((t=e.createElement("base")).id="urlresolver-base",e.head.appendChild(t));var n=e.getElementById("urlresolver-parser");n||((n=e.createElement("a")).id="urlresolver-parser",e.head.appendChild(n)),this.base=t,this.parser=n}return e.prototype.parseUrl=function(e,t){if("undefined"!=typeof URL)try{e||(e=document.baseURI);var n=e?new URL(t,e):new URL(t);if(n.href)return n}catch(e){}return this.parseUrlUsingBaseAndAnchor(e,t)},e.prototype.parseUrlUsingBaseAndAnchor=function(e,t){this.base.setAttribute("href",e),this.parser.setAttribute("href",t);var n=document.createElement("a");return n.href=this.parser.href,n},e.prototype.resolveUrl=function(e,t){return this.parseUrl(e,t).href},e.prototype.resolveToDocument=function(e,t){var n=Jn(e);return null==n?t:this.resolveUrl(n,t)},e}());function Xn(e,t){return Qn.parseUrl(e,t)}function Jn(e){var t=e.document,n=e.location.href;if("string"==typeof t.baseURI)n=t.baseURI;else{var r=t.getElementsByTagName("base")[0];r&&r.href&&(n=r.href)}return"about:blank"==n&&e.parent!=e?Jn(e.parent):n}var $n=new RegExp("[^\\s]"),Zn=new RegExp("[\\s]*$");String.prototype;function er(e){var t=$n.exec(e);if(!t)return e;for(var n=t.index,r=(t=Zn.exec(e))?e.length-t.index:0,i="\uFFFF",o=e.slice(n,e.length-r).split(/\r\n?|\n/g),s=0;sir?(Mt.sendToBugsnag("Ignoring huge text node","warning",{length:r}),""):e.parentNode&&"style"==on(e.parentNode)?n:t.mask?er(n):n}function sr(e){return tr[e]||e.toLowerCase()}function ar(e,t,n,r){var i,o=on(t);if(null===o)return null;var s=function(e){var t,r,s;i=null!==(r=null===(t=nr[e][o])||void 0===t?void 0:t[n])&&void 0!==r?r:null===(s=nr[e]["*"])||void 0===s?void 0:s[n]};if(s("Any"),void 0===i){var a=xn(t);if(!a)return null;a.watchKind==an.Exclude?s("Exclude"):a.mask&&s("Mask")}if(void 0===i)return r;switch(i){case"erase":return null;case"scrubUrl":return ur(r,e,{source:"dom",type:o});case"maskText":return er(r);default:return Object(cn.assertExhaustive)(i);}}function ur(e,t,n){switch(n.source){case"dom":switch(r=n.type){case"frame":case"iframe":return hr(e,t);default:return cr(e,t);}case"event":switch(r=n.type){case R.AJAX_REQUEST:case R.NAVIGATE:return cr(e,t);case R.SET_FRAME_BASE:return hr(e,t);default:return Object(cn.assertExhaustive)(r);}case"log":return hr(e,t);case"page":var r;switch(r=n.type){case"base":return hr(e,t);case"referrer":case"url":return cr(e,t);default:return Object(cn.assertExhaustive)(r);}case"perfEntry":switch(n.type){case"frame":case"iframe":case"navigation":case"other":return hr(e,t);default:return cr(e,t);}default:return Object(cn.assertExhaustive)(n);}}function cr(e,t){return lr(e,t,function(e){if(!(e in pr)){var t=["password","token","^jwt$"];switch("4K3FQ"!==e&&"NQ829"!==e&&"KCF98"!==e&&t.push("^code$"),e){case"2FVM4":t.push("^e$","^eref$","^fn$");break;case"35500":t.push("share_token","password-reset-key");break;case"1HWDJ":t.push("email_id","invite","join");break;case"J82WF":t=[".*"];break;case"8MM83":t=["^creditCard"];break;case"PAN8Z":t.push("code","hash","ol","aeh");break;case"BKP05":t.push("api_key","session_id","encryption_key");break;case"QKM7G":t.push("postcode","encryptedQuoteId","registrationId","productNumber","customerName","agentId","qqQuoteId");break;case"FP60X":t.push("phrase");break;case"GDWG7":t=["^(?!productType|utmSource).*$"];break;case"RV68C":t.push("drivingLicense");break;case"S3VEC":t.push("data");break;case"Q8RZE":t.push("myLowesCardNumber");}pr[e]=new RegExp(t.join("|"),"i")}return pr[e]}(t))}function hr(e,t){return lr(e,t,fr)}function dr(e,t,n,r){var i=new RegExp("(\\/"+t+"\\/).*$","i");n==r&&e.pathname.indexOf(t)>=0&&(e.pathname=e.pathname.replace(i,"$1"+rr))}function lr(e,t,n){var r=Xn("",e);return r.hash&&r.hash.indexOf("access_token")>=0&&(r.hash="#"+rr),dr(r,"visitor",t,"QS8RG"),dr(r,"account",t,"QS8RG"),dr(r,"parentAccount",t,"QS8RG"),dr(r,"reset_password",t,"AGQFM"),dr(r,"reset-password",t,"95NJ7"),dr(r,"dl",t,"RV68C"),dr(r,"retailer",t,"FP60X"),dr(r,"ocadotech",t,"FP60X"),dr(r,"serviceAccounts",t,"FP60X"),dr(r,"signup",t,"7R98D"),r.search&&r.search.length>0&&(r.search=function(e,t){return e.split("?").map(function(e){return function(e,t){return e.replace("?","").split("&").map(function(e){return e.split("=")}).map(function(e){var n=e[0],r=e[1],i=e.slice(2);return t.test(n)&&void 0!==r?[n,rr].concat(i):[n,r].concat(i)}).map(function(e){var t=e[0],n=e[1],r=e.slice(2);return void 0===n?t:[t,n].concat(r).join("=")}).join("&")}(e,t)}).join("?")}(r.search,n)),r.href.substring(0,2048)}var pr={};var fr=new RegExp(".*","i");var vr=/([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)/gi,_r=/(?:(http)|(ftp)|(file))[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+/gi;function gr(e){return"function"==typeof(t=e.constructor)&&Function.prototype.toString.call(t).indexOf("[native code]")>-1;var t}var mr=function(){function e(e,t,n){this._watcher=e,this._resizer=t,this._orgId=n,Tn={},kn=1}return e.prototype.tokenizeNode=function(e,t,n,r,i,o,s){var a=this,u=xn(t),c=xn(n),h=[];return function(e){var t=kn;try{return e(),!0}catch(e){return kn=t,!1}}(function(){a.tokeNode(e,u,c,r,h,i,o,s)})||(h=[]),h},e.prototype.tokeNode=function(e,t,n,r,i,o,s,a){for(var u=[{parentMirror:t,nextMirror:n,node:r}],c=function(){var t=u.pop();if(!t)return"continue";if("string"==typeof t)return i.push(t),"continue";var n=t.parentMirror,r=t.nextMirror,c=t.node,d=h._encodeTagAndAttributes(e,n,r,c,i,o,s);if(null==d||d.watchKind===an.Exclude)return"continue";var l=c.nodeType===pn?c.shadowRoot:null;return(l||c.firstChild)&&a(d)?(u.push("]"),function(e,t){if(!e)return;var n=[];ft(e,function(e){return n.push(e)});for(;n.length>0;){var r=n.pop();r&&t(r)}}(c.firstChild,function(e){u.push({parentMirror:d,nextMirror:null,node:e})}),l&&u.push({parentMirror:d,nextMirror:null,node:l}),void u.push("[")):"continue"},h=this;u.length;)c()},e.prototype._encodeTagAndAttributes=function(e,t,n,r,i,o,s){if("script"==on(r)||8==r.nodeType)return null;var a,u,c,h,d=function(e){return e.constructor===window.ShadowRoot}(r),l=function(e){var t={id:kn++,node:e};return Tn[t.id]=t,e._fs=t.id,t}(r);if((d||(null==t?void 0:t.isInShadowDOM))&&(l.isInShadowDOM=!0),t&&(d?t.shadow=l:(a=t,u=l,c=n,h=this._resizer,Fn(u,h),u.parent=a,u.next=c,c&&(u.prev=c.prev,c.prev=u),null==u.next?(u.prev=a.lastChild,a.lastChild=u):u.next.prev=u,null==u.prev?a.child=u:u.prev.next=u)),10==r.nodeType){var p=r;return i.push("1?s.push([o.idx,a]):s.push(o.idx):s.push(i)}for(n=1;n1e3)return null;if(!n||n.nodeType!=pn)return null;var r=n;if(getComputedStyle(r).display.indexOf("inline")<0)return r;n=n.parentNode}},t}(Er),kr=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return br(t,e),t.prototype.observe=function(e){var t=this;if(e&&e.nodeType==pn){var n=e;this.growWatchedIndex(xn(e)),this._ctx.measurer.requestMeasureTask(function(){t.addEntry(n)})}},t.prototype.unobserveSubtree=function(e){var t=xn(e);t&&this.clearWatchedIndex(t)},t.prototype.nodeChanged=function(e){var t=this,n=this.relatedWatched(e);this._ctx.measurer.requestMeasureTask(function(){for(var e=0,r=n;e0){var r=zr(t[n-1],e);if(r)return void(t[n-1]=r)}else!function(e){qr.push(e),Pr||(Pr=!0,Mr(Ur))}(this.observer);t[n]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this.handleEventBound,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this.handleEventBound,!0),t.childList&&e.addEventListener("DOMNodeInserted",this.handleEventBound,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this.handleEventBound,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this.handleEventBound,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this.handleEventBound,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this.handleEventBound,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this.handleEventBound,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=Or.get(e);t||Or.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=Or.get(e),n=0;n0||this._toRefresh.length>0){var n={},r={};for(var i in this.processRecords(e,t,r,n),r){var o=i.split("\t");t.push({Kind:R.MUT_ATTR,When:e,Args:[parseInt(o[0]),o[1],r[i]]})}for(var i in n)t.push({Kind:R.MUT_TEXT,When:e,Args:[parseInt(i),n[i]]})}var s=this._newShadowContainers;this._newShadowContainers=[];for(var a=0;a0)for(var d=0;d0){s[h]=c.target;var p=Jr(c.target);p&&(s[p.id]=p.node)}break;case"characterData":Cn(c.target)||c.oldValue!=c.target.textContent&&(r[h]=or(c.target));break;case"attributes":var f=An(w=c.target);if(_n(this._watcher.isWatched(w))>_n(f)){a(w);break}var v=Xr(c.attributeNamespace)+(c.attributeName||""),_=sr(v);if(w.hasAttribute(v)){var g=c.target.getAttribute(v);c.oldValue!=g&&(g=ar(this._ctx.options.orgId,c.target,_,g||""),this._attrVisitor(c.target,_,g||""),null!==g&&(n[h+"\t"+_]=g))}else n[h+"\t"+_]=null;}}catch(e){}for(var m=0,y=this._toRefresh;m0&&n.push({When:t,Kind:R.MUT_SHADOW,Args:[o,this._compress?this._lz.encode(s):s]})},e.prototype.genInsert=function(e,t,n,r,i,o){var s=Mn(r)||-1,a=Mn(o)||-1,u=-1===s&&-1===a,c=p(),h=this.genDocStream(e,r,i,o),d=p()-c;if(h.length>0){var l=p(),f=this._compress?this._lz.encode(h):h,v=p()-l;n.push({When:t,Kind:R.MUT_INSERT,Args:[s,a,f]},{When:t,Kind:R.TIMING,Args:[[O.Internal,A.Serialization,u?x.DomSnapshot:x.NodeEncoding,t,d,[x.LzEncoding,v]]]})}},e.prototype.genDocStream=function(e,t,n,r){var i=this;if(t&&Cn(t))return[];for(var o=[],s=this._encoder.tokenizeNode(e,t,r,n,function(e){if(e.nodeType==pn){var t=e;t.shadowRoot&&i.observe(t.shadowRoot)}i._nodeVisitor(e,o)},this._attrVisitor,this._visitChildren),a=0,u=o;a0){var i=t[t.length-1];if(i.Kind==R.MUT_REMOVE)return void i.Args.push(r)}t.push({When:e,Kind:R.MUT_REMOVE,Args:[r]})},e.prototype.setUpIEWorkarounds=function(){var t=this;if(ue){var n=Object.getOwnPropertyDescriptor(Node.prototype,"textContent"),r=n&&n.set;if(!n||!r)throw new Error("Missing textContent setter -- not safe to record mutations.");Object.defineProperty(Element.prototype,"textContent",Gr(Gr({},n),{set:function(e){try{for(var t=void 0;t=this.firstChild;)this.removeChild(t);if(null===e||""==e)return;var n=(this.ownerDocument||document).createTextNode(e);this.appendChild(n)}catch(t){r&&r.call(this,e)}}}))}this._setPropertyThrottle=new nn(e.ThrottleMax,e.ThrottleInterval,function(){return new tn(function(){t._setPropertyWasThrottled=!0,t.tearDownIEWorkarounds()}).start()});var i=this._setPropertyThrottle.guard(function(e){e.cssText=e.cssText});this._setPropertyThrottle.open(),this._setPropertyHook=Tt(CSSStyleDeclaration.prototype,"setProperty"),this._setPropertyHook&&this._setPropertyHook.afterSync(function(e){i(e.that)}),this._removePropertyHook=Tt(CSSStyleDeclaration.prototype,"removeProperty"),this._removePropertyHook&&this._removePropertyHook.afterSync(function(e){i(e.that)})},e.prototype.tearDownIEWorkarounds=function(){this._setPropertyThrottle&&this._setPropertyThrottle.close(),this._setPropertyHook&&this._setPropertyHook.disable(),this._removePropertyHook&&this._removePropertyHook.disable()},e.prototype.updateConsent=function(){var e=this,t=xn(this._root);t&&function(e,t){for(var n=[e];n.length;){var r=n.pop();if(r){t(r);for(var i=r.child,o=r.shadow;i;)n.push(i),i=i.next;o&&n.push(o)}}}(t,function(t){var n=t.node;t.matchesAnyConsentRule&&e.refreshElement(n)})},e.prototype.refreshElement=function(e){Mn(e)&&this._toRefresh.push(e)},e.ThrottleMax=1024,e.ThrottleInterval=1e4,e}();function Xr(e){return void 0===e&&(e=""),null===e?"":{"http://www.w3.org/1999/xlink":"xlink:","http://www.w3.org/XML/1998/namespace":"xml:","http://www.w3.org/2000/xmlns/":"xmlns:"}[e]||""}function Jr(e){return!(null==e?void 0:e.shadowRoot)||gr(e.shadowRoot)?null:xn(e.shadowRoot)}var $r=["navigationStart","unloadEventStart","unloadEventEnd","redirectStart","redirectEnd","fetchStart","domainLookupStart","domainLookupEnd","connectStart","connectEnd","secureConnectionStart","requestStart","responseStart","responseEnd","domLoading","domInteractive","domContentLoadedEventStart","domContentLoadedEventEnd","domComplete","loadEventStart","loadEventEnd"],Zr=["name","startTime","duration","initiatorType","redirectStart","redirectEnd","fetchStart","domainLookupStart","domainLookupEnd","connectStart","connectEnd","secureConnectionStart","requestStart","responseStart","responseEnd","unloadEventStart","unloadEventEnd","domInteractive","domContentLoadedEventStart","domContentLoadedEventEnd","domComplete","loadEventStart","loadEventEnd","type","redirectCount","decodedBodySize","encodedBodySize","transferSize"],ei=["name","startTime","duration","initiatorType","redirectStart","redirectEnd","fetchStart","domainLookupStart","domainLookupEnd","connectStart","connectEnd","secureConnectionStart","requestStart","responseStart","responseEnd","decodedBodySize","encodedBodySize","transferSize"],ti=["name","startTime","duration"],ni=["jsHeapSizeLimit","totalJSHeapSize","usedJSHeapSize"],ri=function(){function e(e,t,n){this._ctx=e,this._queue=t,this._perfSupported=!1,this._timingSupported=!1,this._getEntriesSupported=!1,this._memorySupported=!1,this._lastUsedJSHeapSize=0,this._gotLoad=!1,this._observer=null,this._observedBatches=[];var r=window.performance;r&&(this._perfSupported=!0,r.timing&&(this._timingSupported=!0),r.memory&&(this._memorySupported=!0),"function"==typeof r.getEntries&&(this._getEntriesSupported=!0),this._listeners=n.createChild())}return e.prototype.start=function(e){var t=this;this._resourceUploader=e;var n=window.performance;n&&(this._ctx.recording.inFrame||this._queue.enqueue({Kind:R.REC_FEAT_SUPPORTED,Args:[Y.Performance,this._timingSupported,Y.PerformanceEntries,this._getEntriesSupported,Y.PerformanceMemory,this._memorySupported,Y.PerformanceObserver,!!window.PerformanceObserver]}),this.observe(),!this._observer&&n.addEventListener&&n.removeEventListener&&this._listeners.add(n,"resourcetimingbufferfull",!0,function(){t._queue.enqueue({Kind:R.RESOURCE_TIMING_BUFFER_FULL,Args:[]})}),this.checkMemory())},e.prototype.onLoad=function(){this._gotLoad||(this._gotLoad=!0,this._timingSupported&&(this.recordTiming(performance.timing),this.checkForNewEntries()))},e.prototype.tick=function(e){this.checkMemory(),e&&this.checkForNewEntries()},e.prototype.shutdown=function(){this._listeners&&this._listeners.clear(),this._resourceUploader=void 0;var e=[];this._observer?(this._observer.takeRecords&&(e=this._observer.takeRecords()),this._observer.disconnect()):window.performance&&window.performance.getEntries&&(e=window.performance.getEntries()),e.length>300&&(e=e.slice(0,300),this._queue.enqueue({Kind:R.RESOURCE_TIMING_BUFFER_FULL,Args:[]})),this._observedBatches.push(e),this.tick(!0)},e.prototype.observe=function(){var e=this;if(!this._observer&&this._getEntriesSupported&&window.PerformanceObserver){this._observedBatches.push(performance.getEntries()),this._observer=new window.PerformanceObserver(function(t){var n=t.getEntries();e._observedBatches.push(n)});var t=["navigation","resource","measure","mark"];window.PerformancePaintTiming&&t.push("paint"),this._observer.observe({entryTypes:t})}},e.prototype.checkMemory=function(){if(this._memorySupported&&!this._ctx.recording.inFrame){var e=performance.memory;if(e){var t=e.usedJSHeapSize-this._lastUsedJSHeapSize;(0==this._lastUsedJSHeapSize||s.mathAbs(t/this._lastUsedJSHeapSize)>.2)&&(this.addPerfEvent(z.Memory,e,ni),this._lastUsedJSHeapSize=e.usedJSHeapSize)}}},e.prototype.recordEntry=function(e){switch(e.entryType){case"navigation":this.recordNavigation(e);break;case"resource":this.recordResource(e);break;case"paint":this.recordPaint(e);break;case"measure":this.recordMeasure(e);break;case"mark":this.recordMark(e);}},e.prototype.checkForNewEntries=function(){if(this._perfSupported&&this._getEntriesSupported){var e=this._observedBatches;this._observedBatches=[];for(var t=0,n=e;t=t&&(n=void 0),o[o.length-1]--,n&&n!==si&&r&&(o.push(s.objectKeys(n).length),a.push(u));o[o.length-1]<=0;)o.pop(),a.pop();return n})}catch(e){}return"[error serializing "+e.constructor.name+"]"}}var ui=function(){function e(e){this._requestTracker=e}return e.prototype.disable=function(){this._hook&&(this._hook.disable(),this._hook=null)},e.prototype.enable=function(e){var t,n=this,r=I(e),i=null===(t=null==r?void 0:r._w)||void 0===t?void 0:t.fetch;(i||e.fetch)&&(this._hook=Tt(i?r._w:e,"fetch"),this._hook&&this._hook.afterSync(function(e){return n.recordFetch(e.that,e.result,e.args[0],e.args[1])}))},e.prototype.recordFetch=function(e,t,n,r){var i,o="GET",s="",a={};if("string"==typeof n?s=n:"url"in n?(s=n.url,o=n.method,i=n.body,n.headers&&n.headers.forEach(function(e,t){a[e]=t})):s=""+n,r){o=r.method||o;var u=r.headers;if(u)if(nt(u))for(var c=0,h=u;c-1;n&&o?t.clone().text().then(Mt.wrap(function(i){var o=mi(i,n),s=o[0],a=o[1];r.onComplete(e,t,s,a)}))["catch"](Mt.wrap(function(n){r.onComplete(e,t,-1,void 0)})):r.onComplete(e,t,-1,void 0)}))["catch"](Mt.wrap(function(t){r.onComplete(e,t,-1,void 0)}))},e.prototype.onComplete=function(e,t,n,r){var i=this,o=-1,s="";if("headers"in t){o=t.status;s=this.serializeFetchHeaders(t.headers,function(e){return i._requestTracker.isHeaderInResponseHeaderWhitelist(e[0])})}return this._requestTracker.onComplete(e,s,o,n,r)},e.prototype.serializeFetchHeaders=function(e,t){var n="";return e.forEach(function(e,r){r=r.toLowerCase();var i=t([r,e]);n+=r+(i?": "+e:"")+di}),n},e}(),ci=function(){function e(e){this._requestTracker=e}return e.prototype.disable=function(){this._xhrOpenHook&&(this._xhrOpenHook.disable(),this._xhrOpenHook=null),this._xhrSetHeaderHook&&(this._xhrSetHeaderHook.disable(),this._xhrSetHeaderHook=null)},e.prototype.enable=function(e){var t,n=this,r=I(e),i=(null===(t=null==r?void 0:r._w)||void 0===t?void 0:t.XMLHttpRequest)||e.XMLHttpRequest;if(i){var o=i.prototype;this._xhrOpenHook=Tt(o,"open"),this._xhrOpenHook&&this._xhrOpenHook.before(function(e){var t=e.args[0],r=e.args[1];n._requestTracker.addPendingReq(e.that,t,r),e.that.addEventListener("load",Mt.wrap(function(t){n.onComplete(e.that)})),e.that.addEventListener("error",Mt.wrap(function(t){n.onComplete(e.that)}))}),this._xhrSendHook=Tt(o,"send"),this._xhrSendHook&&this._xhrSendHook.before(function(e){var t=e.args[0];n._requestTracker.addRequestBody(e.that,t)}),this._xhrSetHeaderHook=Tt(o,"setRequestHeader"),this._xhrSetHeaderHook&&this._xhrSetHeaderHook.before(function(e){var t=e.args[0],r=e.args[1];n._requestTracker.addHeader(e.that,t,r)})}},e.prototype.onComplete=function(e){var t=this,n=this.responseBody(e),r=n[0],i=n[1],o=Ei(function(e){var t=[];return e.split(di).forEach(function(e){var n=e.indexOf(":");-1!=n?t.push([e.slice(0,n).trim(),e.slice(n+1,e.length).trim()]):t.push([e.trim(),null])}),t}(e.getAllResponseHeaders()),function(e){return t._requestTracker.isHeaderInResponseHeaderWhitelist(e[0])});return this._requestTracker.onComplete(e,o,e.status,r,i)},e.prototype.responseBody=function(e){var t=this._requestTracker.pendingReq(e);if(!t)return[-1,void 0];var n=this._requestTracker.getRspWhitelist(t.url);if(e.responseType){var r=e.response;switch(r||o("Maybe response type was different that expected."),e.responseType){case"text":return mi(e.responseText,n);case"json":return function(e,t){if(!e)return[-1,void 0];return[yi(e),ai(e,te.MaxPayloadLength,t)]}(r,n);case"arraybuffer":return function(e,t){return[e?e.byteLength:-1,t?"[ArrayBuffer]":void 0]}(r,n);case"blob":return function(e,t){return[e?e.size:-1,t?"[Blob]":void 0]}(r,n);case"document":return function(e,t){return[-1,t?"[Document]":void 0]}(0,n);}}return mi(e.responseText,n)},e}();var hi,di="\r\n",li=["a-im","accept","accept-charset","accept-encoding","accept-language","accept-datetime","access-control-request-method,","access-control-request-headers","cache-control","connection","content-length","content-md5","content-type","date","expect","forwarded","from","host","if-match","if-modified-since","if-none-match","if-range","if-unmodified-since","max-forwards","origin","pragma","range","referer","te","user-agent","upgrade","via","warning"],pi=["access-control-allow-origin","access-control-allow-credentials","access-control-expose-headers","access-control-max-age","access-control-allow-methods","access-control-allow-headers","accept-patch","accept-ranges","age","allow","alt-svc","cache-control","connection","content-disposition","content-encoding","content-language","content-length","content-location","content-md5","content-range","content-type","date","delta-base","etag","expires","im","last-modified","link","location","permanent","p3p","pragma","proxy-authenticate","public-key-pins","retry-after","permanent","server","status","strict-transport-security","trailer","transfer-encoding","tk","upgrade","vary","via","warning","www-authenticate","x-frame-options"],fi={BM7A6:["x-b3-traceid"],KD87S:["transactionid"],NHYJM:["x-att-conversationid"],GBNRN:["x-trace-id"],R16RC:["x-request-id"],DE9CX:["x-client","x-client-id","ot-baggage-original-client","x-req-id","x-datadog-trace-id","x-datadog-parent-id","x-datadog-sampling-priority"]},vi={"thefullstory.com":["x-cloud-trace-context"],TN1:["x-cloud-trace-context"],KD87S:["transactionid"],PPE96:["x-b3-traceid"],HWT6H:["x-b3-traceid"],PPEY7:["x-b3-traceid"],PPK3W:["x-b3-traceid"],NHYJM:["x-att-conversationid"],GBNRN:["x-trace-id"],NK5T9:["traceid","requestid"]},_i=function(){function e(e,t){this._ctx=e,this._queue=t,this._enabled=!1,this._tracker=new gi(e,t),this._xhr=new ci(this._tracker),this._fetch=new ui(this._tracker)}return e.prototype.isEnabled=function(){return this._enabled},e.prototype.enable=function(e){this._enabled||(this._enabled=!0,this._queue.enqueue({Kind:R.REC_FEAT_SUPPORTED,Args:[Y.Ajax,!0,Y.AjaxFetch,!!e]}),this._xhr.enable(this._ctx.window),e&&this._fetch.enable(this._ctx.window))},e.prototype.disable=function(){this._enabled&&(this._enabled=!1,this._xhr.disable(),this._fetch.disable())},e.prototype.tick=function(e){this._tracker.tick(e)},e.prototype.setWatches=function(e){this._tracker.setWatches(e)},e}(),gi=function(){function e(e,t){this._ctx=e,this._queue=t,this._reqHeaderWhitelist={},this._rspHeaderWhitelist={},this._pendingReqs={},this._events=[],this._curId=1,this.addHeaderWhitelist(li,pi),this.addHeaderWhitelist(fi[e.options.orgId],vi[e.options.orgId])}return e.prototype.getReqWhitelist=function(e){var t=this.findWhitelistIndexFor(e);return t>=0&&this._reqWhitelist[t]},e.prototype.getRspWhitelist=function(e){var t=this.findWhitelistIndexFor(e);return t>=0&&this._rspWhitelist[t]},e.prototype.isHeaderInRequestHeaderWhitelist=function(e){return e in this._reqHeaderWhitelist},e.prototype.isHeaderInResponseHeaderWhitelist=function(e){return e in this._rspHeaderWhitelist},e.prototype.pushEvent=function(e){this._events.push(e)},e.prototype.setWatches=function(e){var t=this,n=[];this._reqWhitelist=[],this._rspWhitelist=[],e.forEach(function(e){n.push(e.URLRegex),t._reqWhitelist.push(Si(e.RecordReq,e.ReqWhitelist)),t._rspWhitelist.push(Si(e.RecordRsp,e.RspWhitelist))}),this._reqBodyRegex=new RegExp("("+n.join(")|(")+")")},e.prototype.addHeaderWhitelist=function(e,t){var n=this;e&&e.forEach(function(e){return n._reqHeaderWhitelist[e]=!0}),t&&t.forEach(function(e){return n._rspHeaderWhitelist[e]=!0})},e.prototype.tick=function(e){if(e){for(var t=0;te.MaxRuleBytes&&(o("CSSRule too large, inserting dummy instead: "+n.length),n="dummy {}"),this.withEventQueueForSheet(t,function(e){return e.enqueue({Kind:R.CSSRULE_INSERT,Args:"number"==typeof r?[i,[n],r]:[i,[n]]})}))},e.prototype.addDelete=function(e,t){var n=Fi(e,G.Node);n&&this.withEventQueueForSheet(e,function(e){return e.enqueue({Kind:R.CSSRULE_DELETE,Args:[n,t]})})},e.prototype.onDisableSheet=function(e,t){var n=Fi(e,G.Node);n&&this.withEventQueueForSheet(e,function(e){return e.enqueue({Kind:R.DISABLE_STYLESHEET,Args:[n,!!t]})})},e.prototype.withEventQueueForSheet=function(e,t){if(e.ownerNode)return n=this.ctx,r=e.ownerNode,i=t,void((o=I(Ai(r)||n.window))&&"function"==typeof o._withEventQueue&&o._withEventQueue(n.recording.pageSignature(),function(e){i({enqueue:function(t){Ft(null!=e,Ri)&&e.enqueue(t)},enqueueFirst:function(t){Ft(null!=e,Ri)&&e.enqueueFirst(t)}}),e=null}));var n,r,i,o;t(this.queue)},e.prototype.stop=function(){this.throttle.close();for(var e=0,t=this.hooks;e0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]-1)return[n];if("srcset"==t&&("img"==i||"source"==i)){return null!=n.match(/^\s*$/)?[]:n.split(",").map(function(e){return e.trim().split(/\s+/)[0]})}var o=e;if("style"==t&&o.style){var s=o.style.backgroundImage;if(!s)return[];if(s.length>300)return[];var a=[],u=void 0;for(jt.lastIndex=0;u=jt.exec(s);){var c=u[1];c&&a.push(c.trim())}return a}return[]}(e,t,n);r5e5)){var n=ki(Ti(e));if(n){if(n.length>0&&Li.test(t))return 0;var r,i=Gn();ae?(r=i.createElement("style")).textContent=e.textContent:r=i.importNode(e,!0),i.head.appendChild(r);var o=ki(Ti(r));if(i.head.removeChild(r),o)return n.length>o.length?o.length:void 0}}}(o);void 0!==s&&t.push(function(){n._styleSheetWatcher.snapshotEl(o,s)});break;default:"#"!==e.nodeName[0]&&e.nodeName.indexOf("-")>-1&&this._customElementWatcher.onCustomNodeVisited(e);}if("scrollLeft"in e&&"scrollTop"in e){var a=e;this._ctx.measurer.requestMeasureTask(function(){0==a.scrollLeft&&0==a.scrollTop||n.addScroll(a)})}},e.prototype.isSafePointerEvent=function(e){var t=Ki(e);return!!Mn(t)&&!Cn(t)},e.prototype.addMouseMove=function(e){var t=Mn(Ki(e));this._queue.enqueue({Kind:R.MOUSEMOVE,Args:t?[e.clientX,e.clientY,t]:[e.clientX,e.clientY]})},e.prototype.addMouseDown=function(e){this._queue.enqueue({Kind:R.MOUSEDOWN,Args:[e.clientX,e.clientY]})},e.prototype.addMouseUp=function(e){this._queue.enqueue({Kind:R.MOUSEUP,Args:[e.clientX,e.clientY]})},e.prototype.addTouchEvent=function(e,t){if(void 0!==e.changedTouches)for(var n=0;n0)return n[0]}}return e.target}var Vi=/^\s*at .*(\S+\:\d+|native|())/m,zi=/^(eval@)?(\[native code\])?$/;function Yi(e){if(!e||"string"!=typeof e.stack)return[];var t=e;return t.stack.match(Vi)?t.stack.split("\n").filter(function(e){return!!e.match(Vi)}).map(function(e){e.indexOf("(eval ")>-1&&(e=e.replace(/eval code/g,"eval").replace(/(\(eval at [^\()]*)|(\)\,.*$)/g,""));var t=e.replace(/^\s+/,"").replace(/\(eval code/g,"(").replace(/\(native code\)/,"").split(/\s+/).slice(1),n=Qi(t.pop());return Gi(t.join(" "),["eval",""].indexOf(n[0])>-1?"":n[0],n[1],n[2])}):function(e){return e.split("\n").filter(function(e){return!e.match(zi)}).map(function(e){if(e.indexOf(" > eval")>-1&&(e=e.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g,":$1")),-1===e.indexOf("@")&&-1===e.indexOf(":"))return[e,"",-1,-1];var t=e.split("@"),n=Qi(t.pop());return Gi(t.join("@"),n[0],n[1],n[2])})}(t.stack)}function Gi(e,t,n,r){return[e||"",t||"",parseInt(n||"-1"),parseInt(r||"-1")]}function Qi(e){if(!e||-1===e.indexOf(":"))return["","",""];var t=/(.+?)(?:\:(\d+))?(?:\:(\d+))?$/.exec(e.replace(/[\(\)]/g,""));return t?[t[1]||"",t[2]||"",t[3]||""]:["","",""]}var Xi=function(){for(var e=0,t=0,n=arguments.length;t0&&"string"==typeof e.nodeName}(t)?function(e){return e.toString()}(t):void 0===t?"undefined":"object"!=typeof t||null==t?t:t instanceof Error?t.stack||t.name+": "+t.message:void 0;if(void 0!==o)return void 0===(o=s.jsonStringify(o))?0:("\""==o[0]&&(o=to(o,n,"...\"")),o.length<=n?(i.tokens.push(o),o.length):0);if(i.cyclic){i.opath.splice(r);var a=i.opath.lastIndexOf(t);if(a>-1){var u="";return u="\""+to(u,n-2)+"\"",i.tokens.push(u),u.length}i.opath.push(t)}var c=n,h=function(e){return c>=e.length&&(c-=e.length,i.tokens.push(e),!0)},d=function(e){","==i.tokens[i.tokens.length-1]?i.tokens[i.tokens.length-1]=e:h(e)};if(c<2)return 0;if(nt(t)){h("[");for(var l=0;l0;l++){var p=e(t[l],c-1,r+1,i);if(c-=p,0==p&&!h("null"))break;h(",")}d("]")}else{h("{");var f=Ze(t);for(l=0;l0;l++){var v=f[l],_=t[v];if(!h("\""+v+"\":"))break;if(0==(p=e(_,c-1,r+1,i))){i.tokens.pop();break}c-=p,h(",")}d("}")}return n==1/0?1:n-c}(e,t,0,i);var o=i.tokens.join("");return r?function(e,t){var n=t.replace(vr,"");return n=n.replace(_r,function(t){return ur(t,e,{source:"log",type:"debug"})})}(n,o):o}catch(e){return mt(e)}}function Zi(e,t){var n=0;try{s.jsonStringify(e,function(e,r){if(n++>t)throw"break";if("object"==typeof r)return r})}catch(e){return"break"!=e}return!1}var eo=function(e){return isNaN(e)?"Invalid Date":e.toUTCString()},to=function(e,t,n){return void 0===n&&(n="..."),e.length<=t?e:e.length<=n.length||t<=n.length?e.substring(0,t):e.substring(0,t-n.length)+n};var no=function(){for(var e=0,t=0,n=arguments.length;tthis._curveEndMs&&(this._curveEndMs=e.When),this._evts.push(e)},e.prototype.finish=function(e,t){void 0===t&&(t=[]);var n=this._evts.length;if(n<=1)return!1;for(var r=no([this._curveEndMs],t),i=this._evts[0].When,o=this._evts[n-1].When,s=0;s0?this._lastWhen:this._ctx.time.now();this.enqueueAt(t,e),Zt.checkForBrokenSchedulers()},e.prototype.enqueueAt=function(e,t){if(!this._recordingDisabled){e0){var t=e;t.When=this._eventQueue[0].When,this._eventQueue.unshift(t)}else this.enqueue(e)},e.prototype.addUnload=function(e){this._gotUnload||(this._gotUnload=!0,this.enqueue({Kind:R.UNLOAD,Args:[e]}),this.singSwanSong())},e.prototype.shutdown=function(e){this._flush(),this.addUnload(e),this._flush(),this._recordingDisabled=!0,this.stopPipeline()},e.prototype._flush=function(){this.processEvents(),this._transport.flush()},e.prototype.singSwanSong=function(){this._recordingDisabled||(this.processEvents(),this._transport.singSwanSong())},e.prototype.rebaseIframe=function(e){for(var t=0,n=this._eventQueue.length;t0){var d=h[h.length-1].Args[2];if(d)h[0].Args[9]=d}}for(var l in o){o[p=parseInt(l)].finish(R.SCROLL_LAYOUT_CURVE,[p])}for(var l in s){s[p=parseInt(l)].finish(R.SCROLL_VISUAL_OFFSET_CURVE,[p])}for(var l in i){var p;i[p=parseInt(l)].finish(R.TOUCHMOVE_CURVE,[p])}return t&&t.finish(R.RESIZE_VISUAL_CURVE),n}(t);e||(n=n.concat(this._gatherExternalEvents(0!=n.length))),this.ensureFrameIds(n),0!=n.length&&this._transport.enqueueEvents(this._ctx.recording.pageSignature(),n)}},e.prototype.ensureFrameIds=function(e){if(this._frameId)for(var t=this._parentIds,n=t&&t.length>0,r=0;r>>0).toString(16)).slice(-8);return e},e}();function uo(e){var t=new ao(1);return t.writeAscii(e),t.sumAsHex()}function co(e){var t=new Uint8Array(e);return lo(String.fromCharCode.apply(null,t))}var ho="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function lo(e){var t;return(null!==(t=window.btoa)&&void 0!==t?t:po)(e).replace(/\+/g,"-").replace(/\//g,"_")}function po(e){for(var t=String(e),n=[],r=0,i=0,o=0,s=ho;t.charAt(0|o)||(s="=",o%1);n.push(s.charAt(63&r>>8-o%1*8))){if((i=t.charCodeAt(o+=.75))>255)throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");r=r<<8|i}return n.join("")}var fo=function(e,t,n,r){return new(n||(n=Promise))(function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r["throw"](e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,a)}u((r=r.apply(e,t||[])).next())})},vo=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),"throw":a(1),"return":a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r["return"]:o[0]?r["throw"]||((i=r["return"])&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]go?[4,t(mo)]:[3,3]:[3,5];case 2:u.sent(),i=e.now(),u.label=3;case 3:a=new Uint8Array(n,s,Math.min(o-s,_o)),r.write(a),u.label=4;case 4:return s+=_o,[3,1];case 5:return[2,{hash:r.sum(),hasher:r}];}})})}var wo=function(e,t,n,r){return new(n||(n=Promise))(function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r["throw"](e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,a)}u((r=r.apply(e,t||[])).next())})},bo=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),"throw":a(1),"return":a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r["return"]:o[0]?r["throw"]||((i=r["return"])&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]So){var i=ur(e,t,{source:"log",type:"bugsnag"});return Mt.sendToBugsnag("Size of blob resource exceeds limit","warning",{url:i,MaxResourceSizeBytes:So}),void r(null)}(function(e){var t=oo(),n=t.resolve,r=t.promise,i=new FileReader;return i.readAsArrayBuffer(e),i.onload=function(){n(i.result)},i.onerror=function(e){Mt.sendToBugsnag(e,"error"),n(null)},r})(n).then(function(e){r(e?{buffer:e,blob:n,contentType:n.type}:null)})},s.send(),i)}function ko(e,t){var n,r;return wo(this,void 0,et,function(){var i;return bo(this,function(o){switch(o.label){case 0:return i=e.window,(null===(r=null===(n=i.crypto)||void 0===n?void 0:n.subtle)||void 0===r?void 0:r.digest)?[4,i.crypto.subtle.digest({name:"sha-1"},t)]:[3,2];case 1:return[2,{hash:co(o.sent()),algorithm:"sha1"}];case 2:return[4,yo(e.time,so,t)];case 3:return[2,{hash:o.sent().hash,algorithm:"fsnv"}];}})})}var Io=function(e,t,n,r){return new(n||(n=Promise))(function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r["throw"](e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,a)}u((r=r.apply(e,t||[])).next())})},Co=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),"throw":a(1),"return":a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r["return"]:o[0]?r["throw"]||((i=r["return"])&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&(this._regexes=this._joinRegexes(t))},e.prototype.matches=function(e){return!!this._regexes&&this._regexes.test(e)},e.prototype._isValidRegex=function(e){try{return new RegExp(e),!0}catch(t){return Mt.sendToBugsnag("Browser rejected UrlKeep.Regex","error",{expr:e,error:t.toString()}),!1}},e.prototype._joinRegexes=function(e){try{return new RegExp("("+e.join(")|(")+")","i")}catch(t){return Mt.sendToBugsnag("Browser rejected joining UrlKeep.Regexs","error",{exprs:e,error:t.toString()}),null}},e}();function Po(e,t){var n=Mn(e)+" ";return e.id&&(n+="#"+e.id),n+="[src="+ur(e.src,t,{source:"log",type:"debug"})+"]"}var qo,Uo=function(e){var t=e.transport,n=e.frame,r=e.orgId,s=e.scheme,a=e.script,u=e.recHost,c=e.appHost,h=Po(n,r);o("Injecting into Frame "+h);try{if(function(e){return e.id==e.name&&No.test(e.id)}(n))return void o("Blacklisted iframe: "+h);if(function(e){if(!e.contentDocument||!e.contentWindow||!e.contentWindow.location)return!0;return function(e){return!!e.src&&"about:blank"!=e.src&&e.src.indexOf("javascript:")<0}(e)&&e.src!=e.contentWindow.location.href&&"loading"==e.contentDocument.readyState}(n))return void o("Frame not yet loaded: "+h);var d=n.contentWindow,l=n.contentDocument;if(!d||!l)return void o("Missing contentWindow or contentDocument: "+h);if(!l.head)return void o("Missing contentDocument.head: "+h);if(I(d))return void o("FS already defined in Frame contentWindow: "+h+". Ignoring.");d._fs_org=r,d._fs_script=a,d._fs_rec_host=u,d._fs_app_host=c,d._fs_debug=i(),d._fs_run_in_iframe=!0,t&&(d._fs_transport=t);var p=l.createElement("script");p.async=!0,p.crossOrigin="anonymous",p.src=s+"//"+a,"testdrive"==r&&(p.src+="?allowMoo=true"),l.head.appendChild(p)}catch(e){o("iFrame no injecty. Probably not same origin.")}},No=/^fb\d{18}$/;!function(e){e[e.NoInfoYet=1]="NoInfoYet",e[e.Enabled=2]="Enabled",e[e.Disabled=3]="Disabled"}(qo||(qo={}));var Wo=function(){function e(e,t,n,r){var i=this;this._ctx=e,this._transport=n,this._injector=r,this._bundleUploadInterval=te.DefaultBundleUploadInterval,this._iFrames=[],this._pendingChildFrameIdInits=[],this._listeners=new Ut,this._getCurrentSessionEnabled=qo.NoInfoYet,this._resourceUploadingEnabled=!1,this._tickerTasks=[],this._pendingIframes={},this._watcher=new yn,this._queue=new io(e,this._transport,function(e){for(var t=i._eventWatcher.bundleEvents(e),n=void 0;n=i._tickerTasks.pop();)n();return t},t),this._keep=new Lo(e,this._queue),this._eventWatcher=new Di(e,this._queue,this._keep,this._watcher,this._listeners,function(e){i.onFrameCreated(e)},function(e){i.beforeFrameRemoved(e)},new Eo(e,this._queue,new Ao(e))),this._consoleWatcher=new Ji(e,this._queue,this._listeners),this._scheme=e.options.scheme,this._script=e.options.script,this._recHost=e.options.recHost,this._appHost=e.options.appHost,this._orgId=e.options.orgId,this._wnd=e.window}return e.prototype.bundleUploadInterval=function(){return this._bundleUploadInterval},e.prototype.start=function(e,t){var n=this;this._onFullyStarted=t,"onpagehide"in this._wnd?this._listeners.add(this._wnd,"pagehide",!1,function(e){n.onUnload()}):this._listeners.add(this._wnd,"unload",!1,function(e){n.onUnload()}),this._listeners.add(this._wnd,"message",!1,function(e){if("string"==typeof e.data){var t=e.source;n.postMessageReceived(t,Bo(e.data))}});var r=this._wnd.Document?this._wnd.Document.prototype:this._wnd.document;this._docCloseHook=Tt(r,"close"),this._docCloseHook&&this._docCloseHook.afterAsync(function(){n._listeners.refresh()})},e.prototype.queue=function(){return this._queue},e.prototype.eventWatcher=function(){return this._eventWatcher},e.prototype.console=function(){return this._consoleWatcher},e.prototype.onDomLoad=function(){this._eventWatcher.onDomLoad()},e.prototype.onLoad=function(){this._eventWatcher.onLoad()},e.prototype.shutdown=function(e){this._eventWatcher.shutdown(e),this._consoleWatcher.disable(),this._listeners&&this._listeners.clear(),this._docCloseHook&&this._docCloseHook.disable(),this.tellAllFramesTo(["ShutdownFrame"])},e.prototype.tellAllFramesTo=function(e){for(var t=0;t0){for(var e=0;e0&&this._transport.enqueueEvents(r,n);break;case"RequestFrameId":if(!e)return void o("No MessageEvent.source, iframe may have unloaded.");var s=this.iFrameWndToFsId(e);s?(o("Responding to FID request for frame "+s),this._pendingIframes[s]=!1,this.sendFrameIdToInnerFrame(e,s)):o("No FrameId found. Hoping to send one later.");}},e.prototype.sendFrameIdToInnerFrame=function(e,t){var n=this,r=function(){var r=[];0!=n._frameId&&(r=n._parentIds?n._parentIds.concat(n._frameId):[n._frameId]);var i=n._ctx.time.startTime();Do(e,["SetFrameId",t,r,i,n._scheme,n._script,n._appHost,n._orgId,n._pageRsp])};null==this._frameId?this._pendingChildFrameIdInits.push(r):r()},e.prototype.iFrameWndToFsId=function(e){for(var t=0;t=400&&502!==e||202==e||206==e}var jo=function(){return(jo=Object.assign||function(e){for(var t,n=1,r=arguments.length;n2e6))try{localStorage._fs_swan_song=t}catch(e){}},e.prototype._recover=function(){try{if("_fs_swan_song"in localStorage){var e=localStorage._fs_swan_song||localStorage.singSwanSong;delete localStorage._fs_swan_song,delete localStorage.singSwanSong;var t=wt(e);if(!(t.Bundles&&t.UserId&&t.SessionId&&t.PageId))return void o("Malformed swan song found. Ignoring it.");t.OrgId||(t.OrgId=this._identity.orgId()),t.Bundles.length>0&&(o("Sending "+t.Bundles.length+" bundles as prior page swan song"),this.sendSwanSongBundles(t))}}catch(e){o("Error recovering swan-song: "+e)}},e.prototype.sendSwanSongBundles=function(e,t){var n=this;void 0===t&&(t=0);var r=null;if(nt(e.Bundles)&&0!==e.Bundles.length&&void 0!==e.Bundles[0]){1==e.Bundles.length&&(r=this._ctx.time.wallTime()-(e.LastBundleTime||0));this._protocol.bundle({bundle:e.Bundles[0],deltaT:r,orgId:e.OrgId,pageId:e.PageId,serverBundleTime:e.ServerBundleTime,serverPageStart:e.ServerPageStart,sessionId:e.SessionId,userId:e.UserId,isNewSession:e.IsNewSession,win:function(t){o("Sent "+e.Bundles[0].Evts.length+" trailing events from last session as Seq "+e.Bundles[0].Seq),e.Bundles.shift(),e.Bundles.length>0?n.sendSwanSongBundles(jo(jo({},e),{ServerBundleTime:t.BundleTime})):o("Done with prior page swan song")},lose:function(r){Ho(r)?o("Fatal error while sending events, giving up"):(o("Failed to send events from last session, will retry while on this page"),n._lastSwanSongRetryTimeout=new n._timeoutFactory(n.sendSwanSongBundles,n._protocol.exponentialBackoffMs(t,!0),n,e,t+1).start())}})}},e}(),Vo=function(){function e(e,t,n,r){var i=this;void 0===t&&(t=new Ro(e)),void 0===n&&(n=en),void 0===r&&(r=tn),this._ctx=e,this._protocol=t,this._tickerFactory=n,this._backoffRetries=0,this._backoffTime=0,this._bundleSeq=1,this._lastPostTime=0,this._serverBundleTime=0,this._isNewSession=!1,this._largePageSize=16e6,this._outgoingEventQueue=[],this._bundleQueue=[],this._hibernating=!1,this._heartbeatInterval=0,this._lastUserActivity=this._ctx.time.wallTime(),this._finished=!1,this._scheme=e.options.scheme,this._identity=e.recording.identity,this._lastBundleTime=e.time.wallTime(),this._swanSong=new Ko(e,this._protocol,this._identity,r),this._heartbeatTimeout=new r(function(){i.onHeartbeat()}),this._hibernationTimeout=new r(function(){i.onHibernate()},te.PageInactivityTimeout)}return e.prototype.onShutdown=function(e){this._onShutdown=e},e.prototype.scheme=function(){return this._scheme},e.prototype.enqueueEvents=function(e,t){if(this.maybeHibernate(),this._hibernating){if(this._finished)return;for(var n=0,r=t;n0&&this.enqueueNextBundle(!0),this._bundleQueue.length>0||this._pendingBundle)){var e=this._bundleQueue.concat();this._pendingBundle&&e.unshift(this._pendingBundle),this._swanSong.sing({pageId:this._pageId,bundles:e,lastBundleTime:this._lastBundleTime,serverPageStart:this._serverPageStart,serverBundleTime:this._serverBundleTime,isNewSession:this._isNewSession})}},e.prototype.maybeHibernate=function(){this._hibernating||this.calcLastUserActivityDuration()>=te.PageInactivityTimeout+5e3&&this.onHibernate()},e.prototype.calcLastUserActivityDuration=function(){return s.mathFloor(this._ctx.time.wallTime()-this._lastUserActivity)},e.prototype.onHeartbeat=function(){var e=this.calcLastUserActivityDuration();this._outgoingEventQueue.push({When:this._ctx.time.now(),Kind:R.HEARTBEAT,Args:[e]}),this._heartbeatInterval*=2,this._heartbeatInterval>te.HeartbeatMax&&(this._heartbeatInterval=te.HeartbeatMax),this._heartbeatTimeout.start(this._heartbeatInterval)},e.prototype.onHibernate=function(){this._hibernating||(this.calcLastUserActivityDuration()<=2*te.PageInactivityTimeout&&(this._outgoingEventQueue.push({When:this._ctx.time.now(),Kind:R.UNLOAD,Args:[V.Hibernation]}),this.singSwanSong()),this.stopPipeline(),this._hibernating=!0)},e.prototype.enqueueAndSendBundle=function(){this._pendingBundle?this._pendingBundleFailed&&this._sendPendingBundle():0!=this._outgoingEventQueue.length?this.enqueueNextBundle():this.maybeSendNextBundle()},e.prototype.enqueueNextBundle=function(e){void 0===e&&(e=!1);var t={When:this._outgoingEventQueue[0].When,Seq:this._bundleSeq++,Evts:this._outgoingEventQueue};this._outgoingEventQueue=[],this._bundleQueue.push(t),e?this._protocol.bundleBeacon({bundle:t,deltaT:null,orgId:this._identity.orgId(),pageId:this._pageId,serverBundleTime:this._serverBundleTime,serverPageStart:this._serverPageStart,isNewSession:this._isNewSession,sessionId:this._identity.sessionId(),userId:this._identity.userId(),win:function(){},lose:function(){}}):this.maybeSendNextBundle()},e.prototype.maybeSendNextBundle=function(){this._pageId&&this._serverPageStart&&!this._pendingBundle&&0!=this._bundleQueue.length&&(this._pendingBundle=this._bundleQueue.shift(),this._sendPendingBundle())},e.prototype._sendPendingBundle=function(){var e=this,t=this._ctx.time.wallTime();if(!(te._ctx.recording.bundleUploadInterval()&&e.maybeSendNextBundle()},function(t){if(o("Failed to send events."),Ho(t))return 206==t?Mt.sendToBugsnag("Failed to send bundle, probably because of its large size","error"):t>=500&&Mt.sendToBugsnag("Failed to send bundle, recording outage likely","error"),void(e._onShutdown&&e._onShutdown());e._pendingBundleFailed=!0,e._backoffTime=e._lastPostTime+e._protocol.exponentialBackoffMs(e._backoffRetries++,!1)}))}},e.prototype.sendBundle=function(e,t,n){var r=s.mathFloor(this._ctx.time.wallTime()-this._lastUserActivity),i=this._protocol.bundle({bundle:e,deltaT:null,lastUserActivity:r,orgId:this._identity.orgId(),pageId:this._pageId,serverBundleTime:this._serverBundleTime,serverPageStart:this._serverPageStart,isNewSession:this._isNewSession,sessionId:this._identity.sessionId(),userId:this._identity.userId(),win:t,lose:n});i>this._largePageSize&&this._bundleSeq>16&&(o("splitting large page: "+i),this._ctx.recording.splitPage(V.Size))},e}(),zo=function(){var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(t,n)};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),Yo=function(e){function t(t,n,r,i,o){void 0===r&&(r=new Vo(t,n)),void 0===i&&(i=en),void 0===o&&(o=Uo);var s,a,u=e.call(this,t,i,r,o)||this;return u._protocol=n,u._domLoaded=!1,u._recordingDisabled=!1,u._integrationScriptFetched=!1,r.onShutdown(function(){return u.shutdown(V.SettingsBlocked)}),u._doc=u._wnd.document,u._frameId=0,u._identity=t.recording.identity,u._getCurrentSessionEnabled=qo.NoInfoYet,s=u._wnd,a=function(e){if(u._eventWatcher.shutdown(V.Api),e){var t=u._doc.getElementById(e);t&&t.setAttribute("_fs_embed_token",u._embedToken)}},s._fs_shutdown=a,u}return zo(t,e),t.prototype.onDomLoad=function(){var t=this;e.prototype.onDomLoad.call(this),this._domLoaded=!0,this.injectIntegrationScript(function(){t.fireFsReady(t._recordingDisabled)})},t.prototype.getReplayFlags=function(){var e=U(this._wnd);if(/[?&]_fs_force_session=true(&|#|$)/.test(location.search)&&(e+=",forceSession",this._wnd.history)){var t=location.search.replace(/(^\?|&)_fs_force_session=true(&|$)/,function(e,t,n){return n?t:""});this._wnd.history.replaceState({},"",this._wnd.location.href.replace(location.search,t))}return e},t.prototype.start=function(t,n){var r,i,o,s=this;e.prototype.start.call(this,t,n);var a=this.getReplayFlags(),u=Vt(this._doc),c=u[0],h=u[1],d=bt(this._wnd),l=d[0],p=d[1],f="";t||(f=this._identity.userId());var v=null!==(o=null===(i=null===(r=this._ctx)||void 0===r?void 0:r.recording)||void 0===i?void 0:i.preroll)&&void 0!==o?o:-1,_=ur(Jn(this._wnd),this._orgId,{source:"page",type:"base"}),g=ur(this._doc.referrer,this._orgId,{source:"page",type:"referrer"}),m=ur(this._wnd.location.href,this._orgId,{source:"page",type:"url"}),y={OrgId:this._orgId,UserId:f,Url:m,Base:_,Width:c,Height:h,ScreenWidth:l,ScreenHeight:p,Referrer:g,Preroll:v,Doctype:yt(this._doc),CompiledTimestamp:1591209308,AppId:this._identity.appId()};a&&(y.ReplayFlags=a),this._protocol.page(y,function(e){s.handleResponse(e),s.handleIdentity(e.CookieDomain,e.UserIntId,e.SessionIntId,e.PageIntId,e.EmbedToken),s.handleIntegrationScript(e.IntegrationScript),e.PreviewMode&&s.maybeInjectPreviewScript();var t=s._wnd._fs_pagestart;t&&t();var n=!!e.Consented;s._queue.enqueueFirst({Kind:R.SYS_REPORTCONSENT,Args:[n,K.Document]}),s._queue.enqueueFirst({Kind:R.SET_FRAME_BASE,Args:[ur(Jn(s._wnd),s._orgId,{source:"event",type:R.SET_FRAME_BASE}),yt(s._doc)]}),s._queue.startPipeline({pageId:e.PageIntId,serverPageStart:e.PageStart,isNewSession:!!e.IsNewSession}),s.fullyStarted()},function(e,t){t&&t.user_id&&t.cookie_domain&&t.reason_code==$.ReasonBlockedTrafficRamping&&f!=t.user_id&&s.handleIdentity(t.cookie_domain,t.user_id,"","",""),s.disableBecauseRecPageSaidSo()})},t.prototype.handleIntegrationScript=function(e){var t=this;this._integrationScriptFetched=!0,this._integrationScript=e,this.injectIntegrationScript(function(){t.fireFsReady(t._recordingDisabled)})},t.prototype.handleIdentity=function(e,t,n,r,i){var s=this._identity;s.setIds(this._wnd,e,t,n),this._embedToken=i,o("/User,"+s.userId()+"/Session,"+s.sessionId()+"/Page,"+r)},t.prototype.injectIntegrationScript=function(e){if(this._domLoaded&&this._integrationScriptFetched)if(this._integrationScript){var t=this._doc.createElement("script");this._wnd._fs_csp?(t.addEventListener("load",e),t.addEventListener("error",e),t.async=!0,t.src=this._scheme+"//"+this._recHost+"/rec/integrations?OrgId="+this._orgId,this._doc.head.appendChild(t)):(t.text=this._integrationScript,this._doc.head.appendChild(t),e())}else e()},t.prototype.maybeInjectPreviewScript=function(){if(!this._doc.getElementById("FullStory-preview-script")){var e=this._doc.createElement("script");e.id="FullStory-preview-script",e.async=!0,e.src=this._scheme+"//"+this._appHost+"/s/fspreview.js",this._doc.head.appendChild(e)}},t.prototype.disableBecauseRecPageSaidSo=function(){this.shutdown(V.SettingsBlocked),o("Disabling FS."),this._recordingDisabled=!0,this.fireFsReady(this._recordingDisabled)},t}(Wo),Go=function(){function e(e,t){void 0===t&&(t=new Qo(e)),this._wnd=e,this._messagePoster=t}return e.prototype.enqueueEvents=function(e,t){this._messagePoster.postMessage(this._wnd.parent,"EvtBundle",t,e)},e.prototype.startPipeline=function(){},e.prototype.stopPipeline=function(){},e.prototype.flush=function(){},e.prototype.singSwanSong=function(){},e.prototype.onShutdown=function(e){},e}(),Qo=function(){function e(e){this.wnd=e}return e.prototype.postMessage=function(e,t,n,r){var i=N(this.wnd);if(i)try{i.send(t,gt(n),r)}catch(e){i.send(t,gt(n))}else e.postMessage(function(e,t,n){var r=[e,t];n&&r.push(n);return gt({__fs:r})}(t,n,r),"*")},e}();var Xo,Jo=function(){var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(t,n)};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),$o=function(e){function t(t,n,r,i,o){void 0===n&&(n=new Qo(t.window)),void 0===r&&(r=new Go(t.window)),void 0===i&&(i=en),void 0===o&&(o=Uo);var s=e.call(this,t,i,r,o)||this;return s._messagePoster=n,s}return Jo(t,e),t.prototype.start=function(t,n){var r=this;e.prototype.start.call(this,t,n),this.sendRequestForFrameId(),this._listeners.add(this._wnd,"load",!1,function(){r._eventWatcher.recordingIsDetached()&&(o("Recording wrong document. Restarting recording in iframe."),r._ctx.recording.splitPage(V.FsShutdownFrame))})},t.prototype.postMessageReceived=function(t,n){if(e.prototype.postMessageReceived.call(this,t,n),t==this._wnd.parent||t==this._wnd)switch(n[0]){case"GreetFrame":this.sendRequestForFrameId();break;case"SetFrameId":try{var r=n[1],i=n[2],s=n[3],a=n[4],u=n[5],c=n[6],h=n[7],d=n[8];if(!r)return void o("Outer page gave us a bogus frame Id! Iframe: "+ur(location.href,h,{source:"log",type:"debug"}));this.setFrameIdFromOutside(r,i,s,a,u,c,h,d)}catch(e){o("Failed to parse frameId from message: "+gt(n))}break;case"SetConsent":var l=n[1];this.setConsent(l);break;case"InitFrameMobile":try{var p=JSON.parse(n[1]),f=p.StartTime;if(n.length>2){var v=n[2];if(v.hasOwnProperty("ProtocolVersion"))v.ProtocolVersion>=20180723&&v.hasOwnProperty("OuterStartTime")&&(f=v.OuterStartTime)}var _=p.Host;this.setFrameIdFromOutside(0,[],f,"https:",H(_),B(_),p.OrgId,p.PageResponse)}catch(e){o("Failed to initialize mobile web recording from message: "+gt(n))}}},t.prototype.sendRequestForFrameId=function(){this._frameId||(0!=this._frameId?this._wnd.parent?(o("Asking for a frame ID."),this._messagePoster.postMessage(this._wnd.parent,"RequestFrameId",[])):o("Orphaned window."):o("For some reason the outer window attempted to request a frameId"))},t.prototype.setFrameIdFromOutside=function(e,t,n,r,i,s,a,u){if(this._frameId)this._frameId!=e?(o("Updating frame id from "+this._frameId+" to "+e),this._ctx.recording.splitPage(V.FsShutdownFrame)):o("frame Id is already set to "+this._frameId);else{o("FrameId received within frame "+ur(location.href,a,{source:"log",type:"debug"})+": "+e),this._scheme=r,this._script=i,this._appHost=s,this._orgId=a,this._frameId=e,this._parentIds=t,this.handleResponse(u),this.fireFsReady();var c=!!u.Consented;this._queue.enqueueFirst({Kind:R.SYS_REPORTCONSENT,Args:[c,K.Document]}),this._queue.enqueueFirst({Kind:R.SET_FRAME_BASE,Args:[ur(Jn(this._wnd),a,{source:"event",type:R.SET_FRAME_BASE}),yt(this._wnd.document)]}),this._queue.rebaseIframe(n),this._ctx.time.setStartTime(n),this._queue.startPipeline({pageId:this._pageId,serverPageStart:u.PageStart,isNewSession:!!u.IsNewSession,frameId:e,parentIds:t}),this.flushPendingChildFrameInits(),this.fullyStarted()}},t}(Wo),Zo="fsidentity",es="newuid",ts=function(){function e(e,t){void 0===e&&(e=document),void 0===t&&(t=function(){}),this._doc=e,this._onWriteFailure=t,this._cookies={},this._appId=void 0}return e.prototype.initFromCookies=function(e,t){this._cookies=y(this._doc);var n=this._cookies.fs_uid;if(!n)try{n=localStorage._fs_uid}catch(e){}var r=m(n);r&&r.host.replace(/^www\./,"")==e.replace(/^www\./,"")&&r.orgId==t?this._cookie=r:this._cookie={expirationAbsTimeSeconds:g(),host:e,orgId:t,userId:"",sessionId:"",appKeyHash:""}},e.prototype.initFromParsedCookie=function(e){this._cookie=e},e.prototype.clear=function(){this._cookie.userId=this._cookie.sessionId=this._cookie.appKeyHash=this._appId="",this._cookie.expirationAbsTimeSeconds=g(),this._write()},e.prototype.host=function(){return this._cookie.host},e.prototype.orgId=function(){return this._cookie.orgId},e.prototype.userId=function(){return this._cookie.userId},e.prototype.sessionId=function(){return this._cookie.sessionId},e.prototype.appKeyHash=function(){return this._cookie.appKeyHash},e.prototype.cookieData=function(){return this._cookie},e.prototype.cookies=function(){return this._cookies},e.prototype.setCookie=function(e,t,n){void 0===n&&(n=new Date(p()+6048e5).toUTCString());var r=e+"="+t;this._domain?r+="; domain=."+encodeURIComponent(this._domain):r+="; domain=",r+="; Expires="+n+"; path=/; SameSite=Strict","https:"===location.protocol&&(r+="; Secure"),this._doc.cookie=r},e.prototype.setIds=function(e,t,n,r){(C(t)||t.match(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/g))&&(t="");var i=function(e){return e._fs_cookie_domain}(e);"string"==typeof i&&(t=i),this._domain=t,this._cookie.userId=n,this._cookie.sessionId=r,this._write()},e.prototype.clearAppId=function(){return!!this._cookie.appKeyHash&&(this._appId="",this._cookie.appKeyHash="",this._write(),!0)},e.prototype.setAppId=function(e){this._appId=e,this._cookie.appKeyHash=uo(e),this._write()},e.prototype.appId=function(){return this._appId},e.prototype.encode=function(){var e=this._cookie.host+"#"+this._cookie.orgId+"#"+this._cookie.userId+":"+this._cookie.sessionId;return this._cookie.appKeyHash&&(e+="#"+encodeURIComponent(this._cookie.appKeyHash)+"#"),e+="/"+this._cookie.expirationAbsTimeSeconds},e.prototype._write=function(){if(null!=this._domain){var e=this.encode(),t=new Date(1e3*this._cookie.expirationAbsTimeSeconds).toUTCString();this.setCookie("fs_uid",e,t);var n=[];-1===this._doc.cookie.indexOf(e)&&n.push(["fs_uid","cookie"]);try{localStorage._fs_uid=e,localStorage._fs_uid!==e&&n.push(["fs_uid","localStorage"])}catch(e){n.push(["fs_uid","localStorage",String(e)])}n.length>0&&this._onWriteFailure(n)}},e}();!function(e){e.rec="rec",e.user="user",e.account="account",e.consent="consent",e.customEvent="event",e.log="log"}(Xo||(Xo={}));var ns={acctId:"str",displayName:"str",website:"str"},rs={uid:"str",displayName:"str",email:"str"},is={str:os,bool:ss,real:as,"int":us,date:cs,strs:hs(os),bools:hs(ss),reals:hs(as),ints:hs(us),dates:hs(cs),objs:hs(ds),obj:ds};function os(e){return"string"==typeof e}function ss(e){return"boolean"==typeof e}function as(e){return"number"==typeof e}function us(e){return"number"==typeof e&&e-s.mathFloor(e)==0}function cs(e){return!!e&&(e.constructor===Date?!isNaN(e):("number"==typeof e||"string"==typeof e)&&!isNaN(new Date(e)))}function hs(e){return function(t){if(!(t instanceof Array))return!1;for(var n=0;n=0)return o("blocking FS.identify API call; uid value ("+n+") is illegal"),[void 0,Zo];var r=uo(n),i=void 0;t&&t._cookie.appKeyHash&&t._cookie.appKeyHash!==r&&t._cookie.appKeyHash!==n&&(o("user re-identified; existing uid hash ("+t._cookie.appKeyHash+") does not match provided uid ("+n+")"),i=es);return[n,i]}(a,this._identity),c=u[0],h=u[1];if(!c){switch(h){case Zo:case void 0:break;default:o("unexpected failReason returned from setAppId: "+h);}return{events:i}}t.uid=c,this._identity.setAppId(t.uid),h==es&&(r=!0)}}i.push.apply(i,this.rawEventsFromApi(X.User,rs,t,n));break;case Xo.customEvent:var d=t.n,l=t.p;i.push.apply(i,this.rawEventsFromApi(X.Event,{},l,n,d));break;default:o("invalid operation \""+e+"\"; only \"rec\", \"account\", and \"user\" are supported at present");}}catch(t){o("unexpected exception handling "+e+" API call: "+t.message)}return{events:i,reidentify:r}},e.prototype.rawEventsFromApi=function(e,t,n,r,i){var a=function e(t,n,r){var i={PayloadToSend:{},ValidationErrors:[]},a=function(r){var o=e(t,n,r);return i.ValidationErrors=i.ValidationErrors.concat(o.ValidationErrors),o.PayloadToSend};return ht(r,function(e,r){var u=function(e,t,n,r){var i=t,a=typeof n;if("undefined"===a)return o("Cannot infer type of "+a+" "+n),r.push({Type:"vartype",FieldName:t,ValueType:a+" (unsupported)"}),null;if(s.objectHasOwnProp(e,t))return{name:t,type:e[t]};var u=t.lastIndexOf("_");if(-1==u||!vs(t.substring(u+1))){var c=function(e){for(var t in is)if(is[t](e))return t;return null}(n);if(null==c)return o("Cannot infer type of "+a+" "+n),n?r.push({Type:"vartype",FieldName:t}):r.push({Type:"vartype",FieldName:t,ValueType:"null (unsupported)"}),null;u=t.length,o("Warning: Inferring user variable \""+t+"\" to be of type \""+c+"\""),t=t+"_"+c}var h=[t.substring(0,u),t.substring(u+1)],d=h[0],l=h[1];if("object"===a&&!n)return o("null is not a valid object type"),r.push({Type:"vartype",FieldName:i,ValueType:"null (unsupported)"}),null;if(!ls.test(d)){d=d.replace(/[^a-zA-Z0-9_]/g,"").replace(/^[0-9]+/,""),/[0-9]/.test(d[0])&&(d=d.substring(1)),r.push({Type:"varname",FieldName:i});var p=d+"_"+l;if(o("Warning: variable \""+i+"\" has invalid characters. It should match /"+ls.source+"/. Converted name to \""+p+"\"."),""==d)return null;t=p}if(!vs(l))return o("Variable \""+i+"\" has invalid type \""+l+"\""),r.push({Type:"varname",FieldName:i}),null;if(!function(e,t){return is[e](t)}(l,n))return o("illegal value "+gt(n)+" for type "+l),"number"===a?a=n%1==0?"integer":"real":"object"==a&&null!=n&&n.constructor==Date&&(a=isNaN(n)?"invalid date":"date"),r.push({Type:"vartype",FieldName:i,ValueType:a}),null;return{name:t,type:l}}(n,r,e,i.ValidationErrors);if(u){var c=u.name,h=u.type;if("obj"!=h){if("objs"!=h){var d,l;i.PayloadToSend[c]=fs(h,e)}else{t!=X.Event&&i.ValidationErrors.push({Type:"vartype",FieldName:c,ValueType:"Array (unsupported)"});for(var p=e,f=[],v=0;v0&&(i.PayloadToSend[c]=f)}}else{var _=a(e),g=(l="_obj").length>(d=r).length||d.substring(d.length-l.length)!=l?c.substring(0,c.length-"_obj".length):c;i.PayloadToSend[g]=_}}else i.PayloadToSend[r]=fs("",e)}),i}(e,t,n),u=[],c=e==X.Event,h=gt(a.PayloadToSend),d=!!r&&"fs"!==r;return c?u.push({When:0,Kind:R.SYS_CUSTOM,Args:d?[i,h,r]:[i,h]}):u.push({When:0,Kind:R.SYS_SETVAR,Args:d?[e,h,r]:[e,h]}),u},e}();function fs(e,t){return"str"==e&&null!=t&&(t=t.trim()),null==t||"date"!=e&&t.constructor!=Date||(t=function(e){var t,n=e.constructor===Date?e:new Date(e);try{t=n.toISOString()}catch(e){t=null}return t}(t)),t}function vs(e){return!!is[e]}var _s=function(){return(_s=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]u.length?u.length:a.length,d=1,l=d;l=0}var Es=["__zone_symbol__OriginalDelegate","nr@original"];function Ts(e,t){if(t){for(var n=0,r=Es;n16)Mt.sendToBugsnag("Too much synchronous recursion in requestMeasureTask","error");else{var n=this.performingMeasurements?this.recursionDepth:0,r=Mt.wrap(function(){var r=t.recursionDepth;t.recursionDepth=n+1;try{e()}finally{t.recursionDepth=r}});this.measurementTasks?this.measurementTasks.push(r):(this.measurementTasks=[r],this.schedule())}},e.prototype.performMeasurementsNow=function(){this.performMeasurements()},e}(),As=function(e){function t(t,n){var r=e.call(this)||this;return r.wnd=t,r.ResizeObserver=n,r}return Cs(t,e),t.prototype.schedule=function(){var e=this,t=this.ResizeObserver,n=this.wnd.document,r=n.body||n.documentElement||n.head,i=new t(function(){i.unobserve(r),e.performMeasurements()});i.observe(r)},t}(Rs),xs=function(e){function t(t,n,r){var i=e.call(this)||this;return i.wnd=t,i.requestWindowAnimationFrame=n,i.onRAF=Mt.wrap(function(){i.ch.port2.postMessage(void 0)}),i.ch=new r,i}return Cs(t,e),t.prototype.schedule=function(){this.ch.port1.onmessage=this.performMeasurements,this.requestWindowAnimationFrame(this.wnd,this.onRAF)},t}(Rs),Os=function(e){function t(t){var n=e.call(this)||this;return n.wnd=t,n}return Cs(t,e),t.prototype.schedule=function(){s.setWindowTimeout(this.wnd,this.performMeasurements,0)},t}(Rs),Ms=function(e,t,n,r){return new(n||(n=Promise))(function(i,o){function s(e){try{u(r.next(e))}catch(e){o(e)}}function a(e){try{u(r["throw"](e))}catch(e){o(e)}}function u(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,a)}u((r=r.apply(e,t||[])).next())})},Ls=function(e,t){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),"throw":a(1),"return":a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r["return"]:o[0]?r["throw"]||((i=r["return"])&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=8)return void o("reidentified too many times; giving up");this.reidentifyCount++,W(this.wnd,[e,t]),this.splitPage(V.Reidentify,!0)}else u();void 0!==a&&(a?this.restart():this.shutdown(V.Api))}else W(this.wnd,[e,t])},e.prototype._cookies=function(){return this.identity?this.identity.cookies():(o("Error in FS integration: Can't get cookies from inside an iframe"),null)},e.prototype._setCookie=function(e,t){this.identity?this.identity.setCookie(e,t):o("Error in FS integration: Can't set cookies from inside an iframe")},e.prototype._withEventQueue=function(e,t){if(this.recorder){var n=this.recorder.queue(),r=this.recorder.pageSignature();null!=n&&null!=r?e===r?t(n):Mt.sendToBugsnag("Error in _withEventQueue: Page Signature mismatch","error",{pageSignature:r,callerSignature:e}):o("Error getting event queue or page signature: Recorder not initialized")}else o("Error in FS integration: Recorder not initialized")},e.prototype.initApi=function(){var e=I(this.wnd);e?(e.getCurrentSessionURL=_t(this.getCurrentSessionURL,this),e.getCurrentSession=_t(this.getCurrentSession,this),e.enableConsole=_t(this.enableConsole,this),e.disableConsole=_t(this.disableConsole,this),e.log=_t(this.log,this),e.shutdown=_t(this.shutdownApi,this),e.restart=_t(this.restart,this),e._api=_t(this._api,this),e._cookies=_t(this._cookies,this),e._setCookie=_t(this._setCookie,this),e._withEventQueue=_t(this._withEventQueue,this)):o("Missing browser API namespace; couldn't initialize API.")},e.prototype.start=function(){var e,t=this;e=L(this.wnd),r=e,o("script version UNSET (compiled at 1591209308)");var n=P(this.wnd);if(n){this.orgId=n;var i,s=(i=this.wnd)._fs_script||H(D(i));if(s){this.script=s;var a=F(this.wnd);if(a){this.recHost=a;var u=function(e){return e._fs_app_host||B(D(e))}(this.wnd);u?(this.appHost=u,o("script: "+this.script),o("recording host: "+this.recHost),o("orgid: "+this.orgId),"localhost:8080"==this.recHost&&(this.scheme="http:"),this.inFrame()||(this.identity=new ts(this.wnd.document,function(e){for(var n,r=0,i=e;r { describe('full_story', () => { - it('allows orgId when enabled: false', () => { + it('allows org_id when enabled: false', () => { expect(() => config.schema.validate({ full_story: { enabled: false, org_id: 'asdf' } }) ).not.toThrow(); }); - it('rejects undefined or empty orgId when enabled: true', () => { + it('rejects undefined or empty org_id when enabled: true', () => { expect(() => config.schema.validate({ full_story: { enabled: true } }) ).toThrowErrorMatchingInlineSnapshot( @@ -28,7 +28,7 @@ describe('xpack.cloud config', () => { ); }); - it('accepts orgId when enabled: true', () => { + it('accepts org_id when enabled: true', () => { expect(() => config.schema.validate({ full_story: { enabled: true, org_id: 'asdf' } }) ).not.toThrow(); diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index fea8be9f934e1..4c0b7b7f7eca6 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -11,6 +11,7 @@ import { CloudConfigType } from './config'; import { registerCloudUsageCollector } from './collectors'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { parseDeploymentIdFromDeploymentUrl } from './utils'; +import { registerFullstoryRoute } from './routes/fullstory'; interface PluginsSetup { usageCollection?: UsageCollectionSetup; @@ -40,6 +41,13 @@ export class CloudPlugin implements Plugin { const isCloudEnabled = getIsCloudEnabled(this.config.id); registerCloudUsageCollector(usageCollection, { isCloudEnabled }); + if (this.config.full_story.enabled) { + registerFullstoryRoute({ + httpResources: core.http.resources, + packageInfo: this.context.env.packageInfo, + }); + } + return { cloudId: this.config.id, deploymentId: parseDeploymentIdFromDeploymentUrl(this.config.deployment_url), diff --git a/x-pack/plugins/cloud/server/routes/fullstory.test.ts b/x-pack/plugins/cloud/server/routes/fullstory.test.ts new file mode 100644 index 0000000000000..dae541a8c033c --- /dev/null +++ b/x-pack/plugins/cloud/server/routes/fullstory.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('fs/promises'); +import { renderFullStoryLibraryFactory, FULLSTORY_LIBRARY_PATH } from './fullstory'; +import fs from 'fs/promises'; + +const fsMock = fs as jest.Mocked; + +describe('renderFullStoryLibraryFactory', () => { + beforeEach(() => { + jest.resetAllMocks(); + fsMock.readFile.mockResolvedValue(Buffer.from('fake fs src')); + }); + afterAll(() => jest.restoreAllMocks()); + + it('successfully returns file contents', async () => { + const render = renderFullStoryLibraryFactory(); + + const { body } = await render(); + expect(fsMock.readFile).toHaveBeenCalledWith(FULLSTORY_LIBRARY_PATH); + expect(body.toString()).toEqual('fake fs src'); + }); + + it('only reads from file system once callback is invoked', async () => { + const render = renderFullStoryLibraryFactory(); + expect(fsMock.readFile).not.toHaveBeenCalled(); + await render(); + expect(fsMock.readFile).toHaveBeenCalledTimes(1); + }); + + it('does not read from filesystem on subsequent calls', async () => { + const render = renderFullStoryLibraryFactory(); + await render(); + expect(fsMock.readFile).toHaveBeenCalledTimes(1); + await render(); + expect(fsMock.readFile).toHaveBeenCalledTimes(1); + await render(); + expect(fsMock.readFile).toHaveBeenCalledTimes(1); + }); + + it('returns max-age cache-control in dist', async () => { + const render = renderFullStoryLibraryFactory(true); + const { headers } = await render(); + expect(headers).toEqual({ + 'cache-control': 'max-age=31536000', + }); + }); + + it('returns must-revalidate cache-control and sha1 etag in dev', async () => { + const render = renderFullStoryLibraryFactory(false); + const { headers } = await render(); + expect(headers).toEqual({ + 'cache-control': 'must-revalidate', + etag: '1e02f94b45750ba9284c111d31ae7e59c13b8e6e', + }); + }); +}); diff --git a/x-pack/plugins/cloud/server/routes/fullstory.ts b/x-pack/plugins/cloud/server/routes/fullstory.ts new file mode 100644 index 0000000000000..c614d803eed9f --- /dev/null +++ b/x-pack/plugins/cloud/server/routes/fullstory.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; +import fs from 'fs/promises'; +import { createHash } from 'crypto'; +import { once } from 'lodash'; +import { HttpResources, HttpResponseOptions, PackageInfo } from '../../../../../src/core/server'; + +const MINUTE = 60; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; + +/** @internal exported for testing */ +export const FULLSTORY_LIBRARY_PATH = path.join(__dirname, '..', 'assets', 'fullstory_library.js'); + +/** @internal exported for testing */ +export const renderFullStoryLibraryFactory = (dist = true) => + once( + async (): Promise<{ + body: Buffer; + headers: HttpResponseOptions['headers']; + }> => { + const srcBuffer = await fs.readFile(FULLSTORY_LIBRARY_PATH); + const hash = createHash('sha1'); + hash.update(srcBuffer); + const hashDigest = hash.digest('hex'); + + return { + body: srcBuffer, + // In dist mode, return a long max-age, otherwise use etag + must-revalidate + headers: dist + ? { 'cache-control': `max-age=${DAY * 365}` } + : { 'cache-control': 'must-revalidate', etag: hashDigest }, + }; + } + ); + +export const registerFullstoryRoute = ({ + httpResources, + packageInfo, +}: { + httpResources: HttpResources; + packageInfo: Readonly; +}) => { + const renderFullStoryLibrary = renderFullStoryLibraryFactory(packageInfo.dist); + + /** + * Register a custom JS endpoint in order to acheive best caching possible with `max-age` similar to plugin bundles. + */ + httpResources.register( + { + // Use the build number in the URL path to leverage max-age caching on production builds + path: `/internal/cloud/${packageInfo.buildNum}/fullstory.js`, + validate: false, + options: { + authRequired: false, + }, + }, + async (context, req, res) => { + try { + return res.renderJs(await renderFullStoryLibrary()); + } catch (e) { + return res.customError({ + body: `Could not load FullStory library from disk due to error: ${e.toString()}`, + statusCode: 500, + }); + } + } + ); +}; diff --git a/x-pack/test/cloud_integration/config.ts b/x-pack/test/cloud_integration/config.ts new file mode 100644 index 0000000000000..a012dfd1ad34b --- /dev/null +++ b/x-pack/test/cloud_integration/config.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test'; +import { services } from '../functional/services'; +import { pageObjects } from '../functional/page_objects'; + +const FULLSTORY_ORG_ID = process.env.FULLSTORY_ORG_ID; +const FULLSTORY_API_KEY = process.env.FULLSTORY_API_KEY; +const RUN_FULLSTORY_TESTS = Boolean(FULLSTORY_ORG_ID && FULLSTORY_API_KEY); + +// the default export of config files must be a config provider +// that returns an object with the projects config values +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const kibanaFunctionalConfig = await readConfigFile( + require.resolve('../../../test/functional/config.js') + ); + + const kibanaPort = kibanaFunctionalConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/saml_provider/metadata.xml'); + const samlIdPPlugin = resolve(__dirname, './fixtures/saml/saml_provider'); + + return { + testFiles: [...(RUN_FULLSTORY_TESTS ? [resolve(__dirname, './tests/fullstory')] : [])], + + services, + pageObjects, + + servers: kibanaFunctionalConfig.get('servers'), + + esTestCluster: { + license: 'trial', + from: 'snapshot', + serverArgs: [ + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.realms.saml.saml1.order=0', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=http://saml.elastic-cloud.com/attributes/principal', + 'xpack.security.authc.realms.saml.saml1.attributes.groups=http://saml.elastic-cloud.com/attributes/roles', + ], + }, + + kbnTestServer: { + ...kibanaCommonConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaCommonConfig.get('kbnTestServer.serverArgs'), + `--plugin-path=${samlIdPPlugin}`, + '--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d', + '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', + '--xpack.security.authc.selector.enabled=false', + '--xpack.security.authc.providers.saml.saml1.order=0', + '--xpack.security.authc.providers.saml.saml1.realm=saml1', + '--xpack.security.authc.providers.basic.basic1.order=1', + ...(RUN_FULLSTORY_TESTS + ? [ + '--xpack.cloud.full_story.enabled=true', + `--xpack.cloud.full_story.org_id=${FULLSTORY_ORG_ID}`, + ] + : []), + ], + }, + uiSettings: { + defaults: { + 'accessibility:disableAnimations': true, + 'dateFormat:tz': 'UTC', + }, + }, + apps: kibanaFunctionalConfig.get('apps'), + screenshots: { directory: resolve(__dirname, 'screenshots') }, + + junit: { + reportName: 'Chrome X-Pack Cloud Integration Functional Tests (SAML)', + }, + }; +} diff --git a/x-pack/test/cloud_integration/constants.ts b/x-pack/test/cloud_integration/constants.ts new file mode 100644 index 0000000000000..95c5477fbe97b --- /dev/null +++ b/x-pack/test/cloud_integration/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const CLOUD_USER_ID = `1112244`; diff --git a/x-pack/test/cloud_integration/fixtures/saml/saml_provider/kibana.json b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/kibana.json new file mode 100644 index 0000000000000..81ec23fc3d2f3 --- /dev/null +++ b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "samlProviderPlugin", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false +} diff --git a/x-pack/test/cloud_integration/fixtures/saml/saml_provider/metadata.xml b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/metadata.xml new file mode 100644 index 0000000000000..19a6c13264144 --- /dev/null +++ b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/metadata.xml @@ -0,0 +1,41 @@ + + + + + + + + MIIDOTCCAiGgAwIBAgIVANNWkg9lzNiLqNkMFhFKHcXyaZmqMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMCAXDTE5MTIyNzE3MDM0MloYDzIwNjkxMjE0MTcwMzQyWjARMQ8w +DQYDVQQDEwZraWJhbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ +wYYbQtbRBKJ4uNZc2+IgRU+7NNL21ZebQlEIMgK7jAqOMrsW2b5DATz41Fd+GQFU +FUYYjwo+PQj6sJHshOJo/gNb32HrydvMI7YPvevkszkuEGCfXxQ3Dw2RTACLgD0Q +OCkwHvn3TMf0loloV/ePGWaZDYZaXi3a5DdWi/HFFoJysgF0JV2f6XyKhJkGaEfJ +s9pWX269zH/XQvGNx4BEimJpYB8h4JnDYPFIiQdqj+sl2b+kS1hH9kL5gBAMXjFU +vcNnX+PmyTjyJrGo75k0ku+spBf1bMwuQt3uSmM+TQIXkvFDmS0DOVESrpA5EC1T +BUGRz6o/I88Xx4Mud771AgMBAAGjYzBhMB0GA1UdDgQWBBQLB1Eo23M3Ss8MsFaz +V+Twcb3PmDAfBgNVHSMEGDAWgBQa7SYOe8NGcF00EbwPHA91YCsHSTAUBgNVHREE +DTALgglsb2NhbGhvc3QwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAnEl/ +z5IElIjvkK4AgMPrNcRlvIGDt2orEik7b6Jsq6/RiJQ7cSsYTZf7xbqyxNsUOTxv ++frj47MEN448H2nRvUxH29YR3XygV5aEwADSAhwaQWn0QfWTCZbJTmSoNEDtDOzX +TGDlAoCD9s9Xz9S1JpxY4H+WWRZrBSDM6SC1c6CzuEeZRuScNAjYD5mh2v6fOlSy +b8xJWSg0AFlJPCa3ZsA2SKbNqI0uNfJTnkXRm88Z2NHcgtlADbOLKauWfCrpgsCk +cZgo6yAYkOM148h/8wGla1eX+iE1R72NUABGydu8MSQKvc0emWJkGsC1/KqPlf/O +eOUsdwn1yDKHRxDHyA== + + + + + + + + + + diff --git a/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/index.ts b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/index.ts new file mode 100644 index 0000000000000..4ae669256d3a8 --- /dev/null +++ b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializer } from '../../../../../../../src/core/server'; +import { initRoutes } from './init_routes'; + +export const plugin: PluginInitializer = () => ({ + setup: (core) => initRoutes(core), + start: () => {}, + stop: () => {}, +}); diff --git a/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/init_routes.ts b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/init_routes.ts new file mode 100644 index 0000000000000..6bea9af2ed310 --- /dev/null +++ b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/init_routes.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup } from '../../../../../../../src/core/server'; +import { getSAMLResponse, getSAMLRequestId } from './saml_tools'; + +export function initRoutes(core: CoreSetup) { + const serverInfo = core.http.getServerInfo(); + core.http.resources.register( + { + path: '/saml_provider/login', + validate: false, + options: { authRequired: false }, + }, + async (context, request, response) => { + const samlResponse = await getSAMLResponse({ + inResponseTo: await getSAMLRequestId(request.url.href!), + destination: `${serverInfo.protocol}://${serverInfo.hostname}:${serverInfo.port}/api/security/saml/callback`, + }); + + return response.renderHtml({ + body: ` + + Kibana SAML Login + + + +
+ +
+ + `, + }); + } + ); + + core.http.resources.register( + { path: '/saml_provider/login/submit.js', validate: false, options: { authRequired: false } }, + (context, request, response) => { + return response.renderJs({ body: 'document.getElementById("loginForm").submit();' }); + } + ); + + core.http.resources.register( + { + path: '/saml_provider/logout', + validate: false, + options: { authRequired: false }, + }, + async (context, request, response) => { + return response.redirected({ headers: { location: '/logout?SAMLResponse=something' } }); + } + ); +} diff --git a/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/saml_tools.ts b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/saml_tools.ts new file mode 100644 index 0000000000000..da902d332e6e7 --- /dev/null +++ b/x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/saml_tools.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import crypto from 'crypto'; +import fs from 'fs'; +import { stringify } from 'query-string'; +import url from 'url'; +import zlib from 'zlib'; +import { promisify } from 'util'; +import { parseString } from 'xml2js'; +import { SignedXml } from 'xml-crypto'; +import { KBN_KEY_PATH } from '@kbn/dev-utils'; +import { CLOUD_USER_ID } from '../../../../constants'; + +/** + * @file Defines a set of tools that allow us to parse and generate various SAML XML messages. + * The format of these XML messages is a minimum accepted by Elasticsearch and based on the format + * used by `Auth0` identity provider, `auth0/node-samlp` package and SAML 2.0 Specification: + * http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf. + */ + +const inflateRawAsync = promisify(zlib.inflateRaw); +const deflateRawAsync = promisify(zlib.deflateRaw); +const parseStringAsync = promisify(parseString); + +const signingKey = fs.readFileSync(KBN_KEY_PATH); +const signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + +export async function getSAMLRequestId(urlWithSAMLRequestId: string) { + const inflatedSAMLRequest = (await inflateRawAsync( + Buffer.from( + url.parse(urlWithSAMLRequestId, true /* parseQueryString */).query.SAMLRequest as string, + 'base64' + ) + )) as Buffer; + + const parsedSAMLRequest = (await parseStringAsync(inflatedSAMLRequest.toString())) as any; + return parsedSAMLRequest['saml2p:AuthnRequest'].$.ID; +} + +export async function getSAMLResponse({ + destination, + inResponseTo, + sessionIndex, + username = 'a@b.c', + issuer = 'http://www.elastic.co/saml1', +}: { + destination?: string; + inResponseTo?: string; + sessionIndex?: string; + username?: string; + issuer?: string; +} = {}) { + const issueInstant = new Date().toISOString(); + const notOnOrAfter = new Date(Date.now() + 3600 * 1000).toISOString(); + + const samlAssertionTemplateXML = ` + + ${issuer} + + a@b.c + + + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified + + + + + ${CLOUD_USER_ID} + + + superuser + + + + `; + + const signature = new SignedXml(); + signature.signatureAlgorithm = signatureAlgorithm; + signature.signingKey = signingKey; + + // Adds a reference to a `Assertion` xml element and an array of transform algorithms to be used during signing. + signature.addReference( + `//*[local-name(.)='Assertion']`, + [ + 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', + 'http://www.w3.org/2001/10/xml-exc-c14n#', + ], + 'http://www.w3.org/2001/04/xmlenc#sha256' + ); + + signature.computeSignature(samlAssertionTemplateXML, { + location: { reference: `//*[local-name(.)='Issuer']`, action: 'after' }, + }); + + return Buffer.from( + ` + + ${issuer} + + + ${signature.getSignedXml()} + + ` + ).toString('base64'); +} + +export async function getLogoutRequest({ + destination, + sessionIndex, + issuer = 'http://www.elastic.co/saml1', +}: { + destination: string; + sessionIndex: string; + issuer?: string; +}) { + const issueInstant = new Date().toISOString(); + const logoutRequestTemplateXML = ` + + ${issuer} + a@b.c + ${sessionIndex} + + `; + + // HTTP-Redirect with deflate encoding: + // http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf - section 3.4.4.1 + const deflatedLogoutRequest = (await deflateRawAsync( + Buffer.from(logoutRequestTemplateXML) + )) as Buffer; + + const queryStringParameters: Record = { + SAMLRequest: deflatedLogoutRequest.toString('base64'), + SigAlg: signatureAlgorithm, + }; + + const signer = crypto.createSign('RSA-SHA256'); + signer.update(stringify(queryStringParameters, { sort: false })); + queryStringParameters.Signature = signer.sign(signingKey.toString(), 'base64'); + + return queryStringParameters; +} diff --git a/x-pack/test/cloud_integration/ftr_provider_context.d.ts b/x-pack/test/cloud_integration/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..66d4e37b795ca --- /dev/null +++ b/x-pack/test/cloud_integration/ftr_provider_context.d.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; + +import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/cloud_integration/tests/fullstory.ts b/x-pack/test/cloud_integration/tests/fullstory.ts new file mode 100644 index 0000000000000..1cdad719e94e5 --- /dev/null +++ b/x-pack/test/cloud_integration/tests/fullstory.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import fetch from 'node-fetch'; +import { sha256 } from 'js-sha256'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { CLOUD_USER_ID } from '../constants'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const find = getService('find'); + const PageObjects = getPageObjects(['common']); + + const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); + + describe('Cloud FullStory integration', function () { + before(async () => { + // Create role mapping so user gets superuser access + await getService('esSupertest') + .post('/_security/role_mapping/saml1') + .send({ roles: ['superuser'], enabled: true, rules: { field: { 'realm.name': 'saml1' } } }) + .expect(200); + }); + + it('initializes FullStory', async () => { + await PageObjects.common.navigateToApp('home'); + await find.byCssSelector('[data-test-subj="userMenuButton"]', 20000); + + // Check FullStory library loaded + // @ts-expect-error + expect(await browser.execute(() => typeof window.FSKibana === 'function')).to.eql(true); + }); + + it('records a FullStory session with the associated SAML user', async () => { + // Get session ID once fullstory has initialized + let sessionUrl: string | null = null; + let attempts = 0; + while (sessionUrl === null && attempts < 30) { + // @ts-expect-error + sessionUrl = await browser.execute(() => window.FSKibana.getCurrentSessionURL()); + attempts++; + await delay(1000); + } + expect(typeof sessionUrl).to.eql('string'); + sessionUrl = sessionUrl!.replace('%3A', ':'); // undo encoding so comparisons work with API response + + // Check that the session was recorded in the FS API for the given user based on their hashed ID + const hashedUserId = sha256(CLOUD_USER_ID); + const fsSessions = await fetch( + `https://www.fullstory.com/api/v1/sessions?uid=${hashedUserId}&limit=100`, + { + headers: { + 'content-type': 'application/json', + Authorization: `Basic ${process.env.FULLSTORY_API_KEY}`, + }, + } + ).then((r) => r.json()); + expect(fsSessions.find((s: any) => s.FsUrl === sessionUrl)).not.to.be(undefined); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 448c97ff82469..9d7569b6ab4f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17600,6 +17600,11 @@ js-search@^1.4.3: resolved "https://registry.yarnpkg.com/js-search/-/js-search-1.4.3.tgz#23a86d7e064ca53a473930edc48615b6b1c1954a" integrity sha512-Sny5pf00kX1sM1KzvUC9nGYWXOvBfy30rmvZWeRktpg+esQKedIXrXNee/I2CAnsouCyaTjitZpRflDACx4toA== +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" From 67d4c3184efc3c909ae0e9ca819f3a7429327195 Mon Sep 17 00:00:00 2001 From: Kuldeep M Date: Thu, 24 Jun 2021 20:10:22 +0100 Subject: [PATCH 03/40] [Workplace Search] source connection panel content vertical alignment (#103225) * fix 1786 source connection panel vertical alignment * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx Co-authored-by: Constance Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Constance --- .../components/add_source/configured_sources_list.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx index ac19043a30ca6..6da09acf45cbe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx @@ -70,7 +70,12 @@ export const ConfiguredSourcesList: React.FC = ({ - + Date: Thu, 24 Jun 2021 20:12:52 +0100 Subject: [PATCH 04/40] [Logs UI] Log threshold rule performance improvements (#102650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add optimisations for executor / chart previews Co-authored-by: Felix Stürmer --- .../alerting/logs/log_threshold/types.ts | 108 +++-- .../http_api/log_alerts/chart_preview_data.ts | 9 + .../criterion_preview_chart.tsx | 13 +- .../components/expression_editor/editor.tsx | 25 + .../log_threshold_chart_preview.ts | 54 ++- .../log_threshold_executor.test.ts | 455 ++++++++---------- .../log_threshold/log_threshold_executor.ts | 154 ++++-- 7 files changed, 483 insertions(+), 335 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts index f1e983fc34df8..6da0bb58e4e85 100644 --- a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts +++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts @@ -100,7 +100,7 @@ export enum AlertStates { ERROR, } -const ThresholdRT = rt.type({ +export const ThresholdRT = rt.type({ comparator: ComparatorRT, value: rt.number, }); @@ -240,31 +240,43 @@ const chartPreviewHistogramBucket = rt.type({ doc_count: rt.number, }); +const ChartPreviewBucketsRT = rt.partial({ + histogramBuckets: rt.type({ + buckets: rt.array(chartPreviewHistogramBucket), + }), +}); + // ES query responses // +const hitsRT = rt.type({ + total: rt.type({ + value: rt.number, + }), +}); + +const bucketFieldsRT = rt.type({ + key: rt.record(rt.string, rt.string), + doc_count: rt.number, +}); + +const afterKeyRT = rt.partial({ + after_key: rt.record(rt.string, rt.string), +}); + export const UngroupedSearchQueryResponseRT = rt.intersection([ commonSearchSuccessResponseFieldsRT, rt.intersection([ rt.type({ - hits: rt.type({ - total: rt.type({ - value: rt.number, - }), - }), + hits: hitsRT, }), - // Chart preview buckets rt.partial({ - aggregations: rt.type({ - histogramBuckets: rt.type({ - buckets: rt.array(chartPreviewHistogramBucket), - }), - }), + aggregations: ChartPreviewBucketsRT, }), ]), ]); export type UngroupedSearchQueryResponse = rt.TypeOf; -export const GroupedSearchQueryResponseRT = rt.intersection([ +export const UnoptimizedGroupedSearchQueryResponseRT = rt.intersection([ commonSearchSuccessResponseFieldsRT, rt.type({ aggregations: rt.type({ @@ -272,33 +284,73 @@ export const GroupedSearchQueryResponseRT = rt.intersection([ rt.type({ buckets: rt.array( rt.type({ - key: rt.record(rt.string, rt.string), - doc_count: rt.number, + ...bucketFieldsRT.props, filtered_results: rt.intersection([ rt.type({ doc_count: rt.number, }), - // Chart preview buckets - rt.partial({ - histogramBuckets: rt.type({ - buckets: rt.array(chartPreviewHistogramBucket), - }), - }), + ChartPreviewBucketsRT, ]), }) ), }), - rt.partial({ - after_key: rt.record(rt.string, rt.string), - }), + afterKeyRT, ]), }), - hits: rt.type({ - total: rt.type({ - value: rt.number, - }), + hits: hitsRT, + }), +]); + +export type UnoptimizedGroupedSearchQueryResponse = rt.TypeOf< + typeof UnoptimizedGroupedSearchQueryResponseRT +>; + +export const OptimizedGroupedSearchQueryResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + aggregations: rt.type({ + groups: rt.intersection([ + rt.type({ + buckets: rt.array(rt.intersection([bucketFieldsRT, ChartPreviewBucketsRT])), + }), + afterKeyRT, + ]), }), + hits: hitsRT, }), ]); +export type OptimizedGroupedSearchQueryResponse = rt.TypeOf< + typeof OptimizedGroupedSearchQueryResponseRT +>; + +export const GroupedSearchQueryResponseRT = rt.union([ + UnoptimizedGroupedSearchQueryResponseRT, + OptimizedGroupedSearchQueryResponseRT, +]); + export type GroupedSearchQueryResponse = rt.TypeOf; + +export const isOptimizedGroupedSearchQueryResponse = ( + response: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] +): response is OptimizedGroupedSearchQueryResponse['aggregations']['groups']['buckets'] => { + const result = response[0]; + return result && !result.hasOwnProperty('filtered_results'); +}; + +export const isOptimizableGroupedThreshold = ( + selectedComparator: AlertParams['count']['comparator'], + selectedValue?: AlertParams['count']['value'] +) => { + if (selectedComparator === Comparator.GT) { + return true; + } else if ( + typeof selectedValue === 'number' && + selectedComparator === Comparator.GT_OR_EQ && + selectedValue > 0 + ) { + return true; + } else { + return false; + } +}; diff --git a/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts index e6baca305508e..5f488dd532285 100644 --- a/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts +++ b/x-pack/plugins/infra/common/http_api/log_alerts/chart_preview_data.ts @@ -7,6 +7,7 @@ import * as rt from 'io-ts'; import { + ThresholdRT, countCriteriaRT, timeUnitRT, timeSizeRT, @@ -58,6 +59,14 @@ export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< export const getLogAlertsChartPreviewDataAlertParamsSubsetRT: any = rt.intersection([ rt.type({ criteria: countCriteriaRT, + count: rt.intersection([ + rt.type({ + comparator: ThresholdRT.props.comparator, + }), + rt.partial({ + value: ThresholdRT.props.value, + }), + ]), timeUnit: timeUnitRT, timeSize: timeSizeRT, }), diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx index 4e84cf0f9127c..4fa96ea6828d4 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx @@ -68,6 +68,10 @@ export const CriterionPreview: React.FC = ({ const criteria = field && comparator && value ? [{ field, comparator, value }] : []; const params = { criteria, + count: { + comparator: alertParams.count.comparator, + value: alertParams.count.value, + }, timeSize: alertParams.timeSize, timeUnit: alertParams.timeUnit, groupBy: alertParams.groupBy, @@ -78,7 +82,14 @@ export const CriterionPreview: React.FC = ({ } catch (error) { return null; } - }, [alertParams.timeSize, alertParams.timeUnit, alertParams.groupBy, chartCriterion]); + }, [ + alertParams.timeSize, + alertParams.timeUnit, + alertParams.groupBy, + alertParams.count.comparator, + alertParams.count.value, + chartCriterion, + ]); // Check for the existence of properties that are necessary for a meaningful chart. if (chartAlertParams === null || chartAlertParams.criteria.length === 0) return null; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index ef533f63dc175..4eb0f3e8645cf 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -23,6 +23,7 @@ import { PartialRatioAlertParams, ThresholdType, timeUnitRT, + isOptimizableGroupedThreshold, } from '../../../../../common/alerting/logs/log_threshold/types'; import { decodeOrThrow } from '../../../../../common/runtime_types'; import { ObjectEntries } from '../../../../../common/utility_types'; @@ -255,6 +256,15 @@ export const Editor: React.FC< setHasSetDefaults(true); }); + const shouldShowGroupByOptimizationWarning = useMemo(() => { + const hasSetGroupBy = alertParams.groupBy && alertParams.groupBy.length > 0; + return ( + hasSetGroupBy && + alertParams.count && + !isOptimizableGroupedThreshold(alertParams.count.comparator, alertParams.count.value) + ); + }, [alertParams]); + // Wait until the alert param defaults have been set if (!hasSetDefaults) return null; @@ -299,6 +309,21 @@ export const Editor: React.FC< {alertParams.criteria && isRatioAlert(alertParams.criteria) && criteriaComponent} + {shouldShowGroupByOptimizationWarning && ( + <> + + + {i18n.translate('xpack.infra.logs.alertFlyout.groupByOptimizationWarning', { + defaultMessage: + 'When setting a "group by" we highly recommend using the "{comparator}" comparator for your threshold. This can lead to significant performance improvements.', + values: { + comparator: Comparator.GT, + }, + })} + + + )} + ); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts index 321273c656216..7bf2cb5ea3394 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts @@ -23,6 +23,7 @@ import { UngroupedSearchQueryResponse, GroupedSearchQueryResponse, GroupedSearchQueryResponseRT, + isOptimizedGroupedSearchQueryResponse, } from '../../../../common/alerting/logs/log_threshold/types'; import { decodeOrThrow } from '../../../../common/runtime_types'; import { ResolvedLogSourceConfiguration } from '../../../../common/log_sources'; @@ -97,10 +98,19 @@ const addHistogramAggregationToQuery = ( }; if (isGrouped) { - query.body.aggregations.groups.aggregations.filtered_results = { - ...query.body.aggregations.groups.aggregations.filtered_results, - aggregations: histogramAggregation, - }; + const isOptimizedQuery = !query.body.aggregations.groups.aggregations?.filtered_results; + + if (isOptimizedQuery) { + query.body.aggregations.groups.aggregations = { + ...query.body.aggregations.groups.aggregations, + ...histogramAggregation, + }; + } else { + query.body.aggregations.groups.aggregations.filtered_results = { + ...query.body.aggregations.groups.aggregations.filtered_results, + aggregations: histogramAggregation, + }; + } } else { query.body = { ...query.body, @@ -151,18 +161,34 @@ const getGroupedResults = async ( const processGroupedResults = ( results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] ): Series => { - return results.reduce((series, group) => { - if (!group.filtered_results.histogramBuckets) return series; - const groupName = Object.values(group.key).join(', '); - const points = group.filtered_results.histogramBuckets.buckets.reduce( - (pointsAcc, bucket) => { + const getGroupName = ( + key: GroupedSearchQueryResponse['aggregations']['groups']['buckets'][0]['key'] + ) => Object.values(key).join(', '); + + if (isOptimizedGroupedSearchQueryResponse(results)) { + return results.reduce((series, group) => { + if (!group.histogramBuckets) return series; + const groupName = getGroupName(group.key); + const points = group.histogramBuckets.buckets.reduce((pointsAcc, bucket) => { const { key, doc_count: count } = bucket; return [...pointsAcc, { timestamp: key, value: count }]; - }, - [] - ); - return [...series, { id: groupName, points }]; - }, []); + }, []); + return [...series, { id: groupName, points }]; + }, []); + } else { + return results.reduce((series, group) => { + if (!group.filtered_results.histogramBuckets) return series; + const groupName = getGroupName(group.key); + const points = group.filtered_results.histogramBuckets.buckets.reduce( + (pointsAcc, bucket) => { + const { key, doc_count: count } = bucket; + return [...pointsAcc, { timestamp: key, value: count }]; + }, + [] + ); + return [...series, { id: groupName, points }]; + }, []); + } }; const processUngroupedResults = (results: UngroupedSearchQueryResponse): Series => { diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts index ffabd7ba65f03..55c66f0aabbfb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts @@ -58,6 +58,74 @@ const negativeCriteria: Criterion[] = [ { ...textField, comparator: Comparator.NOT_MATCH_PHRASE }, ]; +const expectedPositiveFilterClauses = [ + { + range: { + numericField: { + gt: 10, + }, + }, + }, + { + range: { + numericField: { + gte: 10, + }, + }, + }, + { + range: { + numericField: { + lt: 10, + }, + }, + }, + { + range: { + numericField: { + lte: 10, + }, + }, + }, + { + term: { + keywordField: { + value: 'error', + }, + }, + }, + { + match: { + textField: 'Something went wrong', + }, + }, + { + match_phrase: { + textField: 'Something went wrong', + }, + }, +]; + +const expectedNegativeFilterClauses = [ + { + term: { + keywordField: { + value: 'error', + }, + }, + }, + { + match: { + textField: 'Something went wrong', + }, + }, + { + match_phrase: { + textField: 'Something went wrong', + }, + }, +]; + const baseAlertParams: Pick = { count: { comparator: Comparator.GT, @@ -102,53 +170,7 @@ describe('Log threshold executor', () => { criteria: positiveCriteria, }; const filters = buildFiltersFromCriteria(alertParams, TIMESTAMP_FIELD); - expect(filters.mustFilters).toEqual([ - { - range: { - numericField: { - gt: 10, - }, - }, - }, - { - range: { - numericField: { - gte: 10, - }, - }, - }, - { - range: { - numericField: { - lt: 10, - }, - }, - }, - { - range: { - numericField: { - lte: 10, - }, - }, - }, - { - term: { - keywordField: { - value: 'error', - }, - }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, - }, - ]); + expect(filters.mustFilters).toEqual(expectedPositiveFilterClauses); }); test('Handles negative criteria', () => { @@ -158,25 +180,7 @@ describe('Log threshold executor', () => { }; const filters = buildFiltersFromCriteria(alertParams, TIMESTAMP_FIELD); - expect(filters.mustNotFilters).toEqual([ - { - term: { - keywordField: { - value: 'error', - }, - }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, - }, - ]); + expect(filters.mustNotFilters).toEqual(expectedNegativeFilterClauses); }); test('Handles time range', () => { @@ -194,7 +198,7 @@ describe('Log threshold executor', () => { describe('ES queries', () => { describe('Query generation', () => { - test('Correctly generates ungrouped queries', () => { + it('Correctly generates ungrouped queries', () => { const alertParams: AlertParams = { ...baseAlertParams, criteria: [...positiveCriteria, ...negativeCriteria], @@ -223,71 +227,9 @@ describe('Log threshold executor', () => { }, }, }, - { - range: { - numericField: { - gt: 10, - }, - }, - }, - { - range: { - numericField: { - gte: 10, - }, - }, - }, - { - range: { - numericField: { - lt: 10, - }, - }, - }, - { - range: { - numericField: { - lte: 10, - }, - }, - }, - { - term: { - keywordField: { - value: 'error', - }, - }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, - }, - ], - must_not: [ - { - term: { - keywordField: { - value: 'error', - }, - }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, - }, + ...expectedPositiveFilterClauses, ], + must_not: [...expectedNegativeFilterClauses], }, }, runtime_mappings: { @@ -304,148 +246,159 @@ describe('Log threshold executor', () => { }); }); - test('Correctly generates grouped queries', () => { - const alertParams: AlertParams = { - ...baseAlertParams, - groupBy: ['host.name'], - criteria: [...positiveCriteria, ...negativeCriteria], - }; - const query = getGroupedESQuery( - alertParams, - TIMESTAMP_FIELD, - FILEBEAT_INDEX, - runtimeMappings - ); - expect(query).toEqual({ - index: 'filebeat-*', - allow_no_indices: true, - ignore_unavailable: true, - body: { - query: { - bool: { - filter: [ - { - range: { - '@timestamp': { - gte: expect.any(Number), - lte: expect.any(Number), - format: 'epoch_millis', + describe('Correctly generates grouped queries', () => { + it('When using an optimizable threshold comparator', () => { + const alertParams: AlertParams = { + ...baseAlertParams, + groupBy: ['host.name'], + criteria: [...positiveCriteria, ...negativeCriteria], + }; + const query = getGroupedESQuery( + alertParams, + TIMESTAMP_FIELD, + FILEBEAT_INDEX, + runtimeMappings + ); + + expect(query).toEqual({ + index: 'filebeat-*', + allow_no_indices: true, + ignore_unavailable: true, + body: { + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + gte: expect.any(Number), + lte: expect.any(Number), + format: 'epoch_millis', + }, }, }, + ...expectedPositiveFilterClauses, + ], + must_not: [...expectedNegativeFilterClauses], + }, + }, + aggregations: { + groups: { + composite: { + size: 2000, + sources: [ + { + 'group-0-host.name': { + terms: { + field: 'host.name', + }, + }, + }, + ], }, - ], + }, + }, + runtime_mappings: { + runtime_field: { + type: 'keyword', + script: { + lang: 'painless', + source: 'emit("a runtime value")', + }, + }, }, + size: 0, }, - aggregations: { - groups: { - composite: { - size: 40, - sources: [ + }); + }); + + it('When not using an optimizable threshold comparator', () => { + const alertParams: AlertParams = { + ...baseAlertParams, + count: { + ...baseAlertParams.count, + comparator: Comparator.LT, + }, + groupBy: ['host.name'], + criteria: [...positiveCriteria, ...negativeCriteria], + }; + + const query = getGroupedESQuery( + alertParams, + TIMESTAMP_FIELD, + FILEBEAT_INDEX, + runtimeMappings + ); + + expect(query).toEqual({ + index: 'filebeat-*', + allow_no_indices: true, + ignore_unavailable: true, + body: { + query: { + bool: { + filter: [ { - 'group-0-host.name': { - terms: { - field: 'host.name', + range: { + '@timestamp': { + gte: expect.any(Number), + lte: expect.any(Number), + format: 'epoch_millis', }, }, }, ], }, - aggregations: { - filtered_results: { - filter: { - bool: { - filter: [ - { - range: { - '@timestamp': { - gte: expect.any(Number), - lte: expect.any(Number), - format: 'epoch_millis', - }, - }, - }, - { - range: { - numericField: { - gt: 10, - }, - }, - }, - { - range: { - numericField: { - gte: 10, - }, - }, - }, - { - range: { - numericField: { - lt: 10, - }, - }, - }, - { - range: { - numericField: { - lte: 10, - }, - }, - }, - { - term: { - keywordField: { - value: 'error', - }, - }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, + }, + aggregations: { + groups: { + composite: { + size: 2000, + sources: [ + { + 'group-0-host.name': { + terms: { + field: 'host.name', }, - ], - must_not: [ - { - term: { - keywordField: { - value: 'error', + }, + }, + ], + }, + aggregations: { + filtered_results: { + filter: { + bool: { + filter: [ + { + range: { + '@timestamp': { + gte: expect.any(Number), + lte: expect.any(Number), + format: 'epoch_millis', + }, }, }, - }, - { - match: { - textField: 'Something went wrong', - }, - }, - { - match_phrase: { - textField: 'Something went wrong', - }, - }, - ], + ...expectedPositiveFilterClauses, + ], + must_not: [...expectedNegativeFilterClauses], + }, }, }, }, }, }, - }, - runtime_mappings: { - runtime_field: { - type: 'keyword', - script: { - lang: 'painless', - source: 'emit("a runtime value")', + runtime_mappings: { + runtime_field: { + type: 'keyword', + script: { + lang: 'painless', + source: 'emit("a runtime value")', + }, }, }, + size: 0, }, - size: 0, - }, + }); }); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index a537801202217..f9d0b5575abfc 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -36,6 +36,8 @@ import { CountCriteria, CountAlertParams, RatioAlertParams, + isOptimizedGroupedSearchQueryResponse, + isOptimizableGroupedThreshold, } from '../../../../common/alerting/logs/log_threshold/types'; import { InfraBackendLibs } from '../../infra_types'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; @@ -57,7 +59,7 @@ type LogThresholdAlertExecutorOptions = AlertExecutorOptions< LogThresholdActionGroups >; -const COMPOSITE_GROUP_SIZE = 40; +const COMPOSITE_GROUP_SIZE = 2000; const checkValueAgainstComparatorMap: { [key: string]: (a: number, b: number) => boolean; @@ -68,6 +70,10 @@ const checkValueAgainstComparatorMap: { [Comparator.LT_OR_EQ]: (a: number, b: number) => a <= b, }; +// The executor execution roughly follows a pattern of: +// ES Query generation -> fetching of results -> processing of results. +// With forks for group_by vs ungrouped, and ratio vs non-ratio. + export const createLogThresholdExecutor = (libs: InfraBackendLibs) => async function ({ services, params }: LogThresholdAlertExecutorOptions) { const { alertInstanceFactory, savedObjectsClient, scopedClusterClient } = services; @@ -277,11 +283,26 @@ type ReducedGroupByResults = ReducedGroupByResult[]; const getReducedGroupByResults = ( results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] ): ReducedGroupByResults => { - return results.reduce((acc, groupBucket) => { - const groupName = Object.values(groupBucket.key).join(', '); - const groupResult = { name: groupName, documentCount: groupBucket.filtered_results.doc_count }; - return [...acc, groupResult]; - }, []); + const getGroupName = ( + key: GroupedSearchQueryResponse['aggregations']['groups']['buckets'][0]['key'] + ) => Object.values(key).join(', '); + + if (isOptimizedGroupedSearchQueryResponse(results)) { + return results.reduce((acc, groupBucket) => { + const groupName = getGroupName(groupBucket.key); + const groupResult = { name: groupName, documentCount: groupBucket.doc_count }; + return [...acc, groupResult]; + }, []); + } else { + return results.reduce((acc, groupBucket) => { + const groupName = getGroupName(groupBucket.key); + const groupResult = { + name: groupName, + documentCount: groupBucket.filtered_results.doc_count, + }; + return [...acc, groupResult]; + }, []); + } }; export const processGroupByResults = ( @@ -430,12 +451,29 @@ export const buildFiltersFromCriteria = ( }; export const getGroupedESQuery = ( - params: Pick & { criteria: CountCriteria }, + params: Pick & { + criteria: CountCriteria; + count: { + comparator: AlertParams['count']['comparator']; + value?: AlertParams['count']['value']; + }; + }, timestampField: string, index: string, runtimeMappings: estypes.MappingRuntimeFields ): estypes.SearchRequest | undefined => { - const { groupBy } = params; + // IMPORTANT: + // For the group by scenario we need to account for users utilizing "less than" configurations + // to attempt to match on "0", e.g. something has stopped reporting. We need to cast a wider net for these + // configurations to try and capture more documents, so that the filtering doesn't make the group "disappear". + // Due to this there are two forks in the group by code, one where we can optimize the filtering early, and one where + // it is an inner aggregation. "Less than" configurations with high cardinality group by fields can cause severe performance + // problems. + + const { + groupBy, + count: { comparator, value }, + } = params; if (!groupBy || !groupBy.length) { return; @@ -446,47 +484,81 @@ export const getGroupedESQuery = ( timestampField ); - const aggregations = { - groups: { - composite: { - size: COMPOSITE_GROUP_SIZE, - sources: groupBy.map((field, groupIndex) => ({ - [`group-${groupIndex}-${field}`]: { - terms: { field }, - }, - })), + if (isOptimizableGroupedThreshold(comparator, value)) { + const aggregations = { + groups: { + composite: { + size: COMPOSITE_GROUP_SIZE, + sources: groupBy.map((field, groupIndex) => ({ + [`group-${groupIndex}-${field}`]: { + terms: { field }, + }, + })), + }, }, - aggregations: { - filtered_results: { - filter: { - bool: { - // Scope the inner filtering back to the unpadded range - filter: [rangeFilter, ...mustFilters], - ...(mustNotFilters.length > 0 && { must_not: mustNotFilters }), + }; + + const body: estypes.SearchRequest['body'] = { + query: { + bool: { + filter: [rangeFilter, ...mustFilters], + ...(mustNotFilters.length > 0 && { must_not: mustNotFilters }), + }, + }, + aggregations, + runtime_mappings: runtimeMappings, + size: 0, + }; + + return { + index, + allow_no_indices: true, + ignore_unavailable: true, + body, + }; + } else { + const aggregations = { + groups: { + composite: { + size: COMPOSITE_GROUP_SIZE, + sources: groupBy.map((field, groupIndex) => ({ + [`group-${groupIndex}-${field}`]: { + terms: { field }, + }, + })), + }, + aggregations: { + filtered_results: { + filter: { + bool: { + // Scope the inner filtering back to the unpadded range + filter: [rangeFilter, ...mustFilters], + ...(mustNotFilters.length > 0 && { must_not: mustNotFilters }), + }, }, }, }, }, - }, - }; + }; - const body: estypes.SearchRequest['body'] = { - query: { - bool: { - filter: [groupedRangeFilter], + const body: estypes.SearchRequest['body'] = { + query: { + bool: { + filter: [groupedRangeFilter], + }, }, - }, - aggregations, - runtime_mappings: runtimeMappings, - size: 0, - }; + aggregations, + runtime_mappings: runtimeMappings, + size: 0, + }; - return { - index, - allow_no_indices: true, - ignore_unavailable: true, - body, - }; + return { + index, + allow_no_indices: true, + ignore_unavailable: true, + body, + }; + } }; export const getUngroupedESQuery = ( From fb3e8f4498680d1d3bc52990572e6d0f0c00c7c2 Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 24 Jun 2021 12:43:26 -0700 Subject: [PATCH 05/40] [Enterprise Search] Product 404 polish pass (#103198) * Refactor NotFound component - shared NotFound becomes NotFoundPrompt - returns only an EuiEmptyPrompt, and individual products/plugins are in charge of their own layout, rather than NotFound doing a bunch of arduous switch handling (also closer to how errorConnecting is a component set per-plugin) - This is both due to the recent page template refactor and the fact that WS has extra complex logic of needing to switch between its kibana layout and personal dashboard layout - logos are still hosted in shared/ since they need extra custom CSS to work correctly sizing wise and in dark mode. I renamed its folder from `assets`->`logos` for extra clarity * [AS] Update current AS routers using NotFound + update EngineRouter to use NotFound * [WS] Update app router - Handle errorConnecting at the topmost level, instead of in WorkplaceSearchConfigured (to simplify various logic/expectations & match App Search) - Simplify isOrganization check to use `useRouteMatch` instead of a regex - Use new NotFound component - Add NotFound component for the personal dashboard router * [WS] Improve Source 404 UX - Add NotFound to SourceRouter + add breadcrumbs for organization views - When an actual source ID 404s, fix blanket redirect to a dashboard aware redirect - personal dashboard 404s should send the user back to personal sources, not organization sources + add a flash message error (similar to how App Search behaves for engine 404s) + harden error status checks (gracefully allow for non-http errors to fall back flashAPIErrors * [WS] Improve Settings 404 UX - This was the only remaining WS route I found that either did not have a 404 or a fallback to some overview page, so I tweaked the redirect order for a graceful redirect (vs a blank page) * Fix settings router test * Move away from custom product logos to OOTB Enterprise Search logo Keeping it simple, etc. RIP in peace fancy logos * [PR feedback] toContain over stringContaining --- .../components/analytics/analytics_router.tsx | 8 +- .../components/engine/engine_router.tsx | 6 +- .../components/not_found/index.ts} | 16 +-- .../components/not_found/not_found.test.tsx | 38 ++++++ .../components/not_found/not_found.tsx | 23 ++++ .../applications/app_search/index.test.tsx | 17 +-- .../public/applications/app_search/index.tsx | 13 +- .../not_found/assets/app_search_logo.tsx | 33 ----- .../assets/workplace_search_logo.tsx | 39 ------ .../applications/shared/not_found/index.ts | 2 +- .../shared/not_found/not_found.test.tsx | 70 ----------- .../shared/not_found/not_found.tsx | 117 ------------------ .../not_found/not_found_prompt.test.tsx | 51 ++++++++ .../shared/not_found/not_found_prompt.tsx | 65 ++++++++++ .../workplace_search/index.test.tsx | 32 ++--- .../applications/workplace_search/index.tsx | 38 +++--- .../content_sources/source_logic.test.ts | 80 ++++++------ .../views/content_sources/source_logic.ts | 12 +- .../content_sources/source_router.test.tsx | 4 +- .../views/content_sources/source_router.tsx | 6 +- .../workplace_search/views/not_found/index.ts | 8 ++ .../views/not_found/not_found.test.tsx | 52 ++++++++ .../views/not_found/not_found.tsx | 33 +++++ .../views/settings/settings_router.test.tsx | 4 +- .../views/settings/settings_router.tsx | 5 +- 25 files changed, 378 insertions(+), 394 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/{shared/not_found/assets/logo.scss => app_search/components/not_found/index.ts} (53%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx index d56fe949431c3..2ed06d68301c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx @@ -8,8 +8,6 @@ import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; -import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { NotFound } from '../../../shared/not_found'; import { ENGINE_ANALYTICS_PATH, ENGINE_ANALYTICS_TOP_QUERIES_PATH, @@ -21,6 +19,7 @@ import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH, } from '../../routes'; import { generateEnginePath, getEngineBreadcrumbs } from '../engine'; +import { NotFound } from '../not_found'; import { ANALYTICS_TITLE } from './constants'; import { @@ -61,10 +60,7 @@ export const AnalyticsRouter: React.FC = () => { - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index da8dd8467bb61..2d1bd32a0fff5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -38,6 +38,7 @@ import { CurationsRouter } from '../curations'; import { DocumentDetail, Documents } from '../documents'; import { EngineOverview } from '../engine_overview'; import { AppSearchPageTemplate } from '../layout'; +import { NotFound } from '../not_found'; import { RelevanceTuning } from '../relevance_tuning'; import { ResultSettings } from '../result_settings'; import { SchemaRouter } from '../schema'; @@ -45,7 +46,7 @@ import { SearchUI } from '../search_ui'; import { SourceEngines } from '../source_engines'; import { Synonyms } from '../synonyms'; -import { EngineLogic } from './'; +import { EngineLogic, getEngineBreadcrumbs } from './'; export const EngineRouter: React.FC = () => { const { @@ -159,6 +160,9 @@ export const EngineRouter: React.FC = () => { )} + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts similarity index 53% rename from x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts index b157f55cbba68..482c1a58faa9c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts @@ -5,18 +5,4 @@ * 2.0. */ -.logo404 { - width: $euiSize * 8; - height: $euiSize * 8; - - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - - &__light { - fill: $euiColorLightShade; - } - - &__dark { - fill: $euiColorMediumShade; - } -} +export { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..6fed726eb5e0b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendAppSearchTelemetry } from '../../../shared/telemetry'; +import { AppSearchPageTemplate } from '../layout'; + +import { NotFound } from './'; + +describe('NotFound', () => { + const wrapper = shallow(); + + it('renders the shared not found prompt', () => { + expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); + }); + + it('renders a telemetry error event', () => { + expect(wrapper.find(SendAppSearchTelemetry).prop('action')).toEqual('error'); + }); + + it('passes optional preceding page chrome', () => { + wrapper.setProps({ pageChrome: ['Engines', 'some-engine'] }); + + expect(wrapper.find(AppSearchPageTemplate).prop('pageChrome')).toEqual([ + 'Engines', + 'some-engine', + '404', + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx new file mode 100644 index 0000000000000..f6165fa192d57 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { PageTemplateProps } from '../../../shared/layout'; +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendAppSearchTelemetry } from '../../../shared/telemetry'; +import { AppSearchPageTemplate } from '../layout'; + +export const NotFound: React.FC = ({ pageChrome = [] }) => { + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 00acea945177a..46596cc5d6765 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -17,7 +17,7 @@ import { Redirect } from 'react-router-dom'; import { shallow, ShallowWrapper } from 'enzyme'; -import { Layout, SideNav, SideNavLink } from '../shared/layout'; +import { SideNav, SideNavLink } from '../shared/layout'; import { rerender } from '../test_helpers'; @@ -83,13 +83,6 @@ describe('AppSearchConfigured', () => { wrapper = shallow(); }); - it('renders with layout', () => { - expect(wrapper.find(Layout)).toHaveLength(1); - expect(wrapper.find(Layout).prop('readOnlyMode')).toBeFalsy(); - expect(wrapper.find(EnginesOverview)).toHaveLength(1); - expect(wrapper.find(EngineRouter)).toHaveLength(1); - }); - it('renders header actions', () => { expect(renderHeaderActions).toHaveBeenCalled(); }); @@ -98,11 +91,9 @@ describe('AppSearchConfigured', () => { expect(AppLogic).toHaveBeenCalledWith(DEFAULT_INITIAL_APP_DATA); }); - it('passes readOnlyMode state', () => { - setMockValues({ myRole: {}, readOnlyMode: true }); - rerender(wrapper); - - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); + it('renders engine routes', () => { + expect(wrapper.find(EnginesOverview)).toHaveLength(1); + expect(wrapper.find(EngineRouter)).toHaveLength(1); }); describe('routes with ability checks', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index d7ddad5683f38..6d049b2015487 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -14,8 +14,7 @@ import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; -import { Layout, SideNav, SideNavLink } from '../shared/layout'; -import { NotFound } from '../shared/not_found'; +import { SideNav, SideNavLink } from '../shared/layout'; import { ROLE_MAPPINGS_TITLE } from '../shared/role_mapping/constants'; @@ -28,6 +27,7 @@ import { ErrorConnecting } from './components/error_connecting'; import { KibanaHeaderActions } from './components/layout'; import { Library } from './components/library'; import { MetaEngineCreation } from './components/meta_engine_creation'; +import { NotFound } from './components/not_found'; import { RoleMappings } from './components/role_mappings'; import { Settings, SETTINGS_TITLE } from './components/settings'; import { SetupGuide } from './components/setup_guide'; @@ -85,7 +85,6 @@ export const AppSearchConfigured: React.FC> = (props) = }, } = useValues(AppLogic(props)); const { renderHeaderActions } = useValues(KibanaLogic); - const { readOnlyMode } = useValues(HttpLogic); useEffect(() => { renderHeaderActions(KibanaHeaderActions); @@ -133,13 +132,7 @@ export const AppSearchConfigured: React.FC> = (props) = )} - } readOnlyMode={readOnlyMode}> - - - - - - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx deleted file mode 100644 index 8eb2059afd3ed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -export const AppSearchLogo: React.FC = () => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx deleted file mode 100644 index df5b1a1118c41..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -export const WorkplaceSearchLogo: React.FC = () => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts index 482c1a58faa9c..8be374d549952 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { NotFound } from './not_found'; +export { NotFoundPrompt } from './not_found_prompt'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx deleted file mode 100644 index 1561224a26e42..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { setMockValues } from '../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui'; - -import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../common/constants'; -import { SetAppSearchChrome } from '../kibana_chrome'; - -import { AppSearchLogo } from './assets/app_search_logo'; -import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; - -import { NotFound } from './'; - -describe('NotFound', () => { - it('renders an App Search 404 view', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find('h2').text()).toEqual('404 error'); - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual(APP_SEARCH_PLUGIN.SUPPORT_URL); - - const logo = prompt.find(AppSearchLogo).dive().shallow(); - expect(logo.type()).toEqual('svg'); - }); - - it('renders a Workplace Search 404 view', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find('h2').text()).toEqual('404 error'); - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual( - WORKPLACE_SEARCH_PLUGIN.SUPPORT_URL - ); - - const logo = prompt.find(WorkplaceSearchLogo).dive().shallow(); - expect(logo.type()).toEqual('svg'); - }); - - it('changes the support URL if the user has a gold+ license', () => { - setMockValues({ hasGoldLicense: true }); - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual('https://support.elastic.co'); - }); - - it('passes down optional custom breadcrumbs', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(SetAppSearchChrome).prop('trail')).toEqual(['Hello', 'World']); - }); - - it('does not render anything without a valid product', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx deleted file mode 100644 index f288961b72de4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useValues } from 'kea'; - -import { - EuiPageContent, - EuiEmptyPrompt, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { - APP_SEARCH_PLUGIN, - WORKPLACE_SEARCH_PLUGIN, - LICENSED_SUPPORT_URL, -} from '../../../../common/constants'; - -import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; -import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; -import { LicensingLogic } from '../licensing'; -import { EuiButtonTo } from '../react_router_helpers'; -import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; - -import { AppSearchLogo } from './assets/app_search_logo'; -import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; -import './assets/logo.scss'; - -interface NotFoundProps { - // Expects product plugin constants (@see common/constants.ts) - product: { - ID: string; - SUPPORT_URL: string; - }; - // Optional breadcrumbs - breadcrumbs?: BreadcrumbTrail; -} - -export const NotFound: React.FC = ({ product = {}, breadcrumbs }) => { - const { hasGoldLicense } = useValues(LicensingLogic); - const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; - - let Logo; - let SetPageChrome; - let SendTelemetry; - - switch (product.ID) { - case APP_SEARCH_PLUGIN.ID: - Logo = AppSearchLogo; - SetPageChrome = SetAppSearchChrome; - SendTelemetry = SendAppSearchTelemetry; - break; - case WORKPLACE_SEARCH_PLUGIN.ID: - Logo = WorkplaceSearchLogo; - SetPageChrome = SetWorkplaceSearchChrome; - SendTelemetry = SendWorkplaceSearchTelemetry; - break; - default: - return null; - } - - return ( - <> - - - - - } - body={ - <> - -

- {i18n.translate('xpack.enterpriseSearch.notFound.title', { - defaultMessage: '404 error', - })} -

-
-

- {i18n.translate('xpack.enterpriseSearch.notFound.description', { - defaultMessage: 'The page you’re looking for was not found.', - })} -

- - } - actions={ - - - - {i18n.translate('xpack.enterpriseSearch.notFound.action1', { - defaultMessage: 'Back to your dashboard', - })} - - - - - {i18n.translate('xpack.enterpriseSearch.notFound.action2', { - defaultMessage: 'Contact support', - })} - - - - } - /> -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx new file mode 100644 index 0000000000000..c21aeff2780b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; + +import { EuiButtonTo } from '../react_router_helpers'; + +import { NotFoundPrompt } from './'; + +describe('NotFoundPrompt', () => { + const subject = (props?: object) => + shallow() + .find(EuiEmptyPrompt) + .dive(); + + it('renders', () => { + const wrapper = subject({ + productSupportUrl: 'https://discuss.elastic.co/c/enterprise-search/app-search/', + }); + + expect(wrapper.find('h1').text()).toEqual('404 error'); + expect(wrapper.find(EuiButtonTo).prop('to')).toEqual('/'); + expect(wrapper.find(EuiButton).prop('href')).toContain('https://discuss.elastic.co'); + }); + + it('renders with a custom "Back to dashboard" link if passed', () => { + const wrapper = subject({ + productSupportUrl: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/', + backToLink: '/workplace_search/p/sources', + }); + + expect(wrapper.find(EuiButtonTo).prop('to')).toEqual('/workplace_search/p/sources'); + }); + + it('renders with a link to our licensed support URL for gold+ licenses', () => { + setMockValues({ hasGoldLicense: true }); + const wrapper = subject(); + + expect(wrapper.find(EuiButton).prop('href')).toEqual('https://support.elastic.co'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx new file mode 100644 index 0000000000000..97debd21ec16c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { LICENSED_SUPPORT_URL } from '../../../../common/constants'; +import { LicensingLogic } from '../licensing'; +import { EuiButtonTo } from '../react_router_helpers'; + +interface Props { + productSupportUrl: string; + backToLink?: string; +} + +export const NotFoundPrompt: React.FC = ({ productSupportUrl, backToLink = '/' }) => { + const { hasGoldLicense } = useValues(LicensingLogic); + const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : productSupportUrl; + + return ( + + {i18n.translate('xpack.enterpriseSearch.notFound.title', { + defaultMessage: '404 error', + })} + + } + body={ +

+ {i18n.translate('xpack.enterpriseSearch.notFound.description', { + defaultMessage: 'The page you’re looking for was not found.', + })} +

+ } + actions={ + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action1', { + defaultMessage: 'Back to your dashboard', + })} + + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action2', { + defaultMessage: 'Contact support', + })} + + + + } + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index 28169afd4bdeb..2743dfc794ec6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -5,17 +5,15 @@ * 2.0. */ -import '../__mocks__/react_router'; import '../__mocks__/shallow_useeffect.mock'; import { setMockValues, setMockActions, mockKibanaValues } from '../__mocks__/kea_logic'; +import { mockUseRouteMatch } from '../__mocks__/react_router'; import React from 'react'; import { Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { Layout } from '../shared/layout'; - import { WorkplaceSearchHeaderActions } from './components/layout'; import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; @@ -38,6 +36,14 @@ describe('WorkplaceSearch', () => { expect(wrapper.find(WorkplaceSearchConfigured)).toHaveLength(1); }); + + it('renders ErrorState', () => { + setMockValues({ errorConnecting: true }); + + const wrapper = shallow(); + + expect(wrapper.find(ErrorState)).toHaveLength(1); + }); }); describe('WorkplaceSearchUnconfigured', () => { @@ -56,12 +62,12 @@ describe('WorkplaceSearchConfigured', () => { beforeEach(() => { jest.clearAllMocks(); setMockActions({ initializeAppData, setContext }); + mockUseRouteMatch.mockReturnValue(false); }); - it('renders layout, chrome, and header actions', () => { + it('renders chrome and header actions', () => { const wrapper = shallow(); - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toBeFalsy(); expect(wrapper.find(Overview)).toHaveLength(1); expect(mockKibanaValues.setChromeIsVisible).toHaveBeenCalledWith(true); @@ -83,22 +89,6 @@ describe('WorkplaceSearchConfigured', () => { expect(mockKibanaValues.renderHeaderActions).not.toHaveBeenCalled(); }); - it('renders ErrorState', () => { - setMockValues({ errorConnecting: true }); - - const wrapper = shallow(); - - expect(wrapper.find(ErrorState)).toHaveLength(1); - }); - - it('passes readOnlyMode state', () => { - setMockValues({ readOnlyMode: true }); - - const wrapper = shallow(); - - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); - }); - it('renders SourceAdded', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 05018be2934b4..2daf677962163 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -6,19 +6,16 @@ */ import React, { useEffect } from 'react'; -import { Route, Redirect, Switch, useLocation } from 'react-router-dom'; +import { Route, Redirect, Switch, useRouteMatch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; -import { Layout } from '../shared/layout'; -import { NotFound } from '../shared/not_found'; import { AppLogic } from './app_logic'; -import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout'; +import { WorkplaceSearchHeaderActions } from './components/layout'; import { GROUPS_PATH, SETUP_GUIDE_PATH, @@ -36,6 +33,7 @@ import { SourcesRouter } from './views/content_sources'; import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; import { GroupsRouter } from './views/groups'; +import { NotFound } from './views/not_found'; import { Overview } from './views/overview'; import { RoleMappings } from './views/role_mappings'; import { Security } from './views/security'; @@ -44,30 +42,33 @@ import { SetupGuide } from './views/setup_guide'; export const WorkplaceSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); - return !config.host ? : ; + const { errorConnecting } = useValues(HttpLogic); + return !config.host ? ( + + ) : errorConnecting ? ( + + ) : ( + + ); }; export const WorkplaceSearchConfigured: React.FC = (props) => { const { hasInitialized } = useValues(AppLogic); const { initializeAppData, setContext } = useActions(AppLogic); const { renderHeaderActions, setChromeIsVisible } = useValues(KibanaLogic); - const { errorConnecting, readOnlyMode } = useValues(HttpLogic); - - const { pathname } = useLocation(); /** * Personal dashboard urls begin with /p/ * EX: http://localhost:5601/app/enterprise_search/workplace_search/p/sources */ - const personalSourceUrlRegex = /^\/p\//g; // matches '/p/*' - const isOrganization = !pathname.match(personalSourceUrlRegex); // TODO: Once auth is figured out, we need to have a check for the equivilent of `isAdmin`. + const isOrganization = !useRouteMatch(PERSONAL_PATH); // TODO: Once auth is figured out, we need to have a check for the equivalent of `isAdmin`. setContext(isOrganization); useEffect(() => { setChromeIsVisible(isOrganization); - }, [pathname]); + }, [isOrganization]); useEffect(() => { if (!hasInitialized) { @@ -95,6 +96,9 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { + + + @@ -113,15 +117,7 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - } restrictWidth readOnlyMode={readOnlyMode}> - {errorConnecting ? ( - - ) : ( - - - - )} - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 03f46830fafc3..2aed64af53f16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -22,8 +22,6 @@ jest.mock('../../app_logic', () => ({ })); import { AppLogic } from '../../app_logic'; -import { NOT_FOUND_PATH } from '../../routes'; - import { SourceLogic } from './source_logic'; describe('SourceLogic', () => { @@ -176,47 +174,55 @@ describe('SourceLogic', () => { expect(initializeFederatedSummarySpy).toHaveBeenCalledWith(contentSource.id); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await expectedAsyncError(promise); + describe('errors', () => { + it('handles generic errors', async () => { + const mockError = Promise.reject('error'); + http.get.mockReturnValue(mockError); - expect(flashAPIErrors).toHaveBeenCalledWith(error); - }); + SourceLogic.actions.initializeSource(contentSource.id); + await expectedAsyncError(mockError); - it('handles not found state', async () => { - const error = { - response: { - error: 'this is an error', - status: 404, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await expectedAsyncError(promise); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); - expect(navigateToUrl).toHaveBeenCalledWith(NOT_FOUND_PATH); - }); + describe('404s', () => { + const mock404 = Promise.reject({ response: { status: 404 } }); - it('renders error messages passed in success response from server', async () => { - const errors = ['ERROR']; - const promise = Promise.resolve({ - ...contentSource, - errors, + it('redirects to the organization sources page on organization views', async () => { + AppLogic.values.isOrganization = true; + http.get.mockReturnValue(mock404); + + SourceLogic.actions.initializeSource('404ing_org_source'); + await expectedAsyncError(mock404); + + expect(navigateToUrl).toHaveBeenCalledWith('/sources'); + expect(setErrorMessage).toHaveBeenCalledWith('Source not found.'); + }); + + it('redirects to the personal dashboard sources page on personal views', async () => { + AppLogic.values.isOrganization = false; + http.get.mockReturnValue(mock404); + + SourceLogic.actions.initializeSource('404ing_personal_source'); + await expectedAsyncError(mock404); + + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources'); + expect(setErrorMessage).toHaveBeenCalledWith('Source not found.'); + }); }); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await promise; - expect(setErrorMessage).toHaveBeenCalledWith(errors); + it('renders error messages passed in success response from server', async () => { + const errors = ['ERROR']; + const promise = Promise.resolve({ + ...contentSource, + errors, + }); + http.get.mockReturnValue(promise); + SourceLogic.actions.initializeSource(contentSource.id); + await promise; + + expect(setErrorMessage).toHaveBeenCalledWith(errors); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 2e6a3c65597ea..0fd44e01ae495 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -20,7 +20,7 @@ import { import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { AppLogic } from '../../app_logic'; -import { NOT_FOUND_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { @@ -155,8 +155,14 @@ export const SourceLogic = kea>({ clearFlashMessages(); } } catch (e) { - if (e.response.status === 404) { - KibanaLogic.values.navigateToUrl(NOT_FOUND_PATH); + if (e?.response?.status === 404) { + const redirect = isOrganization ? SOURCES_PATH : PERSONAL_SOURCES_PATH; + KibanaLogic.values.navigateToUrl(redirect); + setErrorMessage( + i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage', { + defaultMessage: 'Source not found.', + }) + ); } else { flashAPIErrors(e); } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx index afe0d1f89faea..fbc8eb159a7a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -90,7 +90,7 @@ describe('SourceRouter', () => { expect(wrapper.find(Overview)).toHaveLength(1); expect(wrapper.find(SourceSettings)).toHaveLength(1); expect(wrapper.find(SourceContent)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(3); + expect(wrapper.find(Route)).toHaveLength(4); }); it('renders source routes (custom)', () => { @@ -100,6 +100,6 @@ describe('SourceRouter', () => { expect(wrapper.find(DisplaySettingsRouter)).toHaveLength(1); expect(wrapper.find(Schema)).toHaveLength(1); expect(wrapper.find(SchemaChangeErrors)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(6); + expect(wrapper.find(Route)).toHaveLength(7); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index bf68a60757c0d..9f793fcd34fbe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -13,7 +13,7 @@ import { useActions, useValues } from 'kea'; import { AppLogic } from '../../app_logic'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; -import { CUSTOM_SERVICE_TYPE } from '../../constants'; +import { NAV, CUSTOM_SERVICE_TYPE } from '../../constants'; import { REINDEX_JOB_PATH, SOURCE_DETAILS_PATH, @@ -24,6 +24,7 @@ import { getContentSourcePath as sourcePath, getSourcesPath, } from '../../routes'; +import { NotFound } from '../../views/not_found'; import { DisplaySettingsRouter } from './components/display_settings'; import { Overview } from './components/overview'; @@ -85,6 +86,9 @@ export const SourceRouter: React.FC = () => { + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts new file mode 100644 index 0000000000000..482c1a58faa9c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..0e388a73f0e18 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; +import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; + +import { NotFound } from './'; + +describe('NotFound', () => { + it('renders the shared not found prompt', () => { + const wrapper = shallow(); + expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); + }); + + it('renders a telemetry error event', () => { + const wrapper = shallow(); + expect(wrapper.find(SendWorkplaceSearchTelemetry).prop('action')).toEqual('error'); + }); + + it('passes optional preceding page chrome', () => { + const wrapper = shallow(); + expect(wrapper.prop('pageChrome')).toEqual(['Sources', '404']); + }); + + describe('organization views', () => { + it('renders the WorkplaceSearchPageTemplate', () => { + const wrapper = shallow(); + expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate); + }); + }); + + describe('personal views', () => { + it('renders the PersonalDashboardLayout', () => { + const wrapper = shallow(); + expect(wrapper.type()).toEqual(PersonalDashboardLayout); + }); + + it('sets the "Back to dashboard" link to /p/sources', () => { + const wrapper = shallow(); + expect(wrapper.find(NotFoundPrompt).prop('backToLink')).toEqual('/p/sources'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx new file mode 100644 index 0000000000000..ef55668775513 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { PageTemplateProps } from '../../../shared/layout'; +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; +import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; +import { PERSONAL_SOURCES_PATH } from '../../routes'; + +interface Props { + isOrganization?: boolean; + pageChrome?: PageTemplateProps['pageChrome']; +} +export const NotFound: React.FC = ({ isOrganization = true, pageChrome = [] }) => { + const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout; + + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx index 74092f17eadcf..123167f0ad1d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx @@ -25,8 +25,8 @@ import { SettingsRouter } from './settings_router'; describe('SettingsRouter', () => { const initializeSettings = jest.fn(); const NUM_SOURCES = staticSourceData.length; - // Should be 3 routes other than the sources listed Connectors, Customize, & OauthApplication - const NUM_ROUTES = NUM_SOURCES + 3; + // Should be 4 routes other than the sources listed: Connectors, Customize, & OauthApplication, & a redirect + const NUM_ROUTES = NUM_SOURCES + 4; beforeEach(() => { setMockActions({ initializeSettings }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx index f8c8050e20153..d9aeba361d240 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx @@ -11,7 +11,6 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import { useActions } from 'kea'; import { - ORG_SETTINGS_PATH, ORG_SETTINGS_CUSTOMIZE_PATH, ORG_SETTINGS_CONNECTORS_PATH, ORG_SETTINGS_OAUTH_APPLICATION_PATH, @@ -33,7 +32,6 @@ export const SettingsRouter: React.FC = () => { return ( - @@ -48,6 +46,9 @@ export const SettingsRouter: React.FC = () => { ))} + + + ); }; From 0857e620c7565281290c3de4334a6ae0e04fc8d8 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 24 Jun 2021 14:59:10 -0500 Subject: [PATCH 06/40] [Workplace Search] Remove `isFederatedAuth` checks to expose user features (#103278) * Remove isFederated from main app and routes * Expose all overview cards that were hidden for federated auth * Expose all user features that were hidden for groups * Remove remaining isFederatedAuth references * Lint fixes * Add modified test back for Workplace Search * Remove extraCell Co-authored-by: Constance * Remove brackets Co-authored-by: Constance * Update test name Co-authored-by: Constance Co-authored-by: Constance --- .../common/__mocks__/initial_app_data.ts | 1 - .../enterprise_search/common/types/index.ts | 1 - .../workplace_search/app_logic.test.ts | 3 -- .../workplace_search/app_logic.ts | 12 +----- .../workplace_search/index.test.tsx | 6 ++- .../applications/workplace_search/routes.ts | 1 - .../groups/components/group_overview.tsx | 7 +-- .../groups/components/group_row.test.tsx | 7 --- .../views/groups/components/group_row.tsx | 24 ++++------- .../components/group_users_table.test.tsx | 11 +---- .../groups/components/group_users_table.tsx | 11 ++--- .../groups/components/groups_table.test.tsx | 10 +---- .../views/groups/components/groups_table.tsx | 4 +- .../groups/components/table_filters.test.tsx | 9 +--- .../views/groups/components/table_filters.tsx | 10 ++--- .../views/groups/groups.test.tsx | 20 +-------- .../workplace_search/views/groups/groups.tsx | 11 ++--- .../overview/__mocks__/overview_logic.mock.ts | 2 +- .../views/overview/onboarding_steps.test.tsx | 19 ++++---- .../views/overview/onboarding_steps.tsx | 40 ++++++++--------- .../overview/organization_stats.test.tsx | 9 ---- .../views/overview/organization_stats.tsx | 43 ++++++++----------- .../lib/enterprise_search_config_api.test.ts | 1 - .../lib/enterprise_search_config_api.ts | 1 - 24 files changed, 76 insertions(+), 187 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts index 7ea4289b21967..a4e3ada1c06cb 100644 --- a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts +++ b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts @@ -8,7 +8,6 @@ export const DEFAULT_INITIAL_APP_DATA = { readOnlyMode: false, ilmEnabled: true, - isFederatedAuth: false, configuredLimits: { appSearch: { engine: { diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index 68904483720f2..f405c86de18f0 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -17,7 +17,6 @@ import { export interface InitialAppData { readOnlyMode?: boolean; ilmEnabled?: boolean; - isFederatedAuth?: boolean; configuredLimits?: ConfiguredLimits; access?: ProductAccess; appSearch?: AppSearchAccount; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index b2cc835da4ecd..24a156bbd67b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -20,7 +20,6 @@ describe('AppLogic', () => { const DEFAULT_VALUES = { account: {}, hasInitialized: false, - isFederatedAuth: true, isOrganization: false, organization: {}, }; @@ -36,7 +35,6 @@ describe('AppLogic', () => { viewedOnboardingPage: true, }, hasInitialized: true, - isFederatedAuth: false, isOrganization: false, organization: { defaultOrgName: 'My Organization', @@ -61,7 +59,6 @@ describe('AppLogic', () => { expect(AppLogic.values).toEqual({ ...DEFAULT_VALUES, hasInitialized: true, - isFederatedAuth: false, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index 26e1d7fbb93fd..ee1f6a69fa4b9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -16,7 +16,6 @@ import { interface AppValues extends WorkplaceSearchInitialData { hasInitialized: boolean; - isFederatedAuth: boolean; isOrganization: boolean; } interface AppActions { @@ -32,10 +31,7 @@ const emptyAccount = {} as Account; export const AppLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'app_logic'], actions: { - initializeAppData: ({ workplaceSearch, isFederatedAuth }) => ({ - workplaceSearch, - isFederatedAuth, - }), + initializeAppData: ({ workplaceSearch }) => ({ workplaceSearch }), setContext: (isOrganization) => isOrganization, setOrgName: (name: string) => name, setSourceRestriction: (canCreatePersonalSources: boolean) => canCreatePersonalSources, @@ -47,12 +43,6 @@ export const AppLogic = kea>({ initializeAppData: () => true, }, ], - isFederatedAuth: [ - true, - { - initializeAppData: (_, { isFederatedAuth }) => !!isFederatedAuth, - }, - ], isOrganization: [ false, { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index 2743dfc794ec6..3ddccde6abd33 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -6,6 +6,7 @@ */ import '../__mocks__/shallow_useeffect.mock'; +import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__'; import { setMockValues, setMockActions, mockKibanaValues } from '../__mocks__/kea_logic'; import { mockUseRouteMatch } from '../__mocks__/react_router'; @@ -75,9 +76,10 @@ describe('WorkplaceSearchConfigured', () => { }); it('initializes app data with passed props', () => { - shallow(); + const { workplaceSearch } = DEFAULT_INITIAL_APP_DATA; + shallow(); - expect(initializeAppData).toHaveBeenCalledWith({ isFederatedAuth: true }); + expect(initializeAppData).toHaveBeenCalledWith({ workplaceSearch }); }); it('does not re-initialize app data or re-render header actions', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index b9309ffd94809..3dac5f80700c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -50,7 +50,6 @@ export const PERSONAL_PATH = '/p'; export const USERS_AND_ROLES_PATH = '/users_and_roles'; -export const USERS_PATH = '/users'; export const SECURITY_PATH = '/security'; export const GROUPS_PATH = '/groups'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 6914c5dcfcad1..76658cec75e51 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -24,7 +24,6 @@ import { import { i18n } from '@kbn/i18n'; import { TruncatedContent } from '../../../../shared/truncate'; -import { AppLogic } from '../../../app_logic'; import noSharedSourcesIcon from '../../../assets/share_circle.svg'; import { WorkplaceSearchPageTemplate } from '../../../components/layout'; import { ContentSection } from '../../../components/shared/content_section'; @@ -124,8 +123,6 @@ export const GroupOverview: React.FC = () => { confirmDeleteModalVisible, } = useValues(GroupLogic); - const { isFederatedAuth } = useValues(AppLogic); - const truncatedName = name && ( ); @@ -167,7 +164,7 @@ export const GroupOverview: React.FC = () => { {MANAGE_SOURCES_BUTTON_TEXT} ); - const manageUsersButton = !isFederatedAuth && ( + const manageUsersButton = ( {MANAGE_USERS_BUTTON_TEXT} @@ -199,7 +196,7 @@ export const GroupOverview: React.FC = () => { ); - const usersSection = !isFederatedAuth && ( + const usersSection = ( { - beforeEach(() => { - setMockValues({ isFederatedAuth: true }); - }); - it('renders', () => { const wrapper = shallow(); @@ -30,7 +25,6 @@ describe('GroupRow', () => { }); it('renders group users', () => { - setMockValues({ isFederatedAuth: false }); const wrapper = shallow(); expect(wrapper.find(GroupUsers)).toHaveLength(1); @@ -51,7 +45,6 @@ describe('GroupRow', () => { }); it('renders empty users message when no users present', () => { - setMockValues({ isFederatedAuth: false }); const wrapper = shallow(); expect(wrapper.find('.user-group__accounts').text()).toEqual(NO_USERS_MESSAGE); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 204d8f5655172..94d44fde57aed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -7,7 +7,6 @@ import React from 'react'; -import { useValues } from 'kea'; import moment from 'moment'; import { EuiTableRow, EuiTableRowCell, EuiIcon } from '@elastic/eui'; @@ -15,7 +14,6 @@ import { i18n } from '@kbn/i18n'; import { EuiLinkTo } from '../../../../shared/react_router_helpers'; import { TruncatedContent } from '../../../../shared/truncate'; -import { AppLogic } from '../../../app_logic'; import { getGroupPath } from '../../../routes'; import { Group } from '../../../types'; import { MAX_NAME_LENGTH } from '../group_logic'; @@ -50,8 +48,6 @@ export const GroupRow: React.FC = ({ users, usersCount, }) => { - const { isFederatedAuth } = useValues(AppLogic); - const GROUP_UPDATED_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText', { @@ -80,17 +76,15 @@ export const GroupRow: React.FC = ({ )} - {!isFederatedAuth && ( - -
- {usersCount > 0 ? ( - - ) : ( - NO_USERS_MESSAGE - )} -
-
- )} + +
+ {usersCount > 0 ? ( + + ) : ( + NO_USERS_MESSAGE + )} +
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.test.tsx index 0dde2f5eaf7f7..cc5f9c4effd21 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.test.tsx @@ -23,17 +23,10 @@ const group = groups[0]; describe('GroupUsersTable', () => { it('renders', () => { - setMockValues({ isFederatedAuth: true, group }); + setMockValues({ group }); const wrapper = shallow(); expect(wrapper.find(EuiTable)).toHaveLength(1); - expect(wrapper.find(TableHeader).prop('headerItems')).toHaveLength(1); - }); - - it('adds header item for non-federated auth', () => { - setMockValues({ isFederatedAuth: false, group }); - const wrapper = shallow(); - expect(wrapper.find(TableHeader).prop('headerItems')).toHaveLength(2); }); @@ -48,7 +41,7 @@ describe('GroupUsersTable', () => { }); }); - setMockValues({ isFederatedAuth: true, group: { users } }); + setMockValues({ group: { users } }); const wrapper = shallow(); const pagination = wrapper.find(EuiTablePagination); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx index 050aaf1dadf89..3e9e40fef44a6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_users_table.tsx @@ -14,7 +14,6 @@ import { Pager } from '@elastic/eui'; import { USERNAME_LABEL, EMAIL_LABEL } from '../../../../shared/constants'; import { TableHeader } from '../../../../shared/table_header'; -import { AppLogic } from '../../../app_logic'; import { UserRow } from '../../../components/shared/user_row'; import { User } from '../../../types'; import { GroupLogic } from '../group_logic'; @@ -22,14 +21,10 @@ import { GroupLogic } from '../group_logic'; const USERS_PER_PAGE = 10; export const GroupUsersTable: React.FC = () => { - const { isFederatedAuth } = useValues(AppLogic); const { group: { users }, } = useValues(GroupLogic); - const headerItems = [USERNAME_LABEL]; - if (!isFederatedAuth) { - headerItems.push(EMAIL_LABEL); - } + const headerItems = [USERNAME_LABEL, EMAIL_LABEL]; const [firstItem, setFirstItem] = useState(0); const [lastItem, setLastItem] = useState(USERS_PER_PAGE - 1); @@ -58,10 +53,10 @@ export const GroupUsersTable: React.FC = () => { return ( <> - + {users.slice(firstItem, lastItem + 1).map((user: User) => ( - + ))} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.test.tsx index d11b830a8fc4b..cbb028139623c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.test.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiTable, EuiTableHeaderCell } from '@elastic/eui'; +import { EuiTable } from '@elastic/eui'; import { DEFAULT_META } from '../../../../shared/constants'; import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; @@ -27,7 +27,6 @@ const mockValues = { groupsMeta: DEFAULT_META, groups, hasFiltersSet: false, - isFederatedAuth: true, }; describe('GroupsTable', () => { @@ -43,13 +42,6 @@ describe('GroupsTable', () => { expect(wrapper.find(GroupRow)).toHaveLength(1); }); - it('renders extra header for non-federated auth', () => { - setMockValues({ ...mockValues, isFederatedAuth: false }); - const wrapper = shallow(); - - expect(wrapper.find(EuiTableHeaderCell)).toHaveLength(4); - }); - it('handles pagination', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx index deaf223afa6b3..cfb3ed8044235 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx @@ -18,7 +18,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AppLogic } from '../../../app_logic'; import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; import { GroupsLogic } from '../groups_logic'; @@ -53,7 +52,6 @@ export const GroupsTable: React.FC<{}> = () => { groups, hasFiltersSet, } = useValues(GroupsLogic); - const { isFederatedAuth } = useValues(AppLogic); const clearFiltersLink = hasFiltersSet ? : undefined; @@ -79,7 +77,7 @@ export const GroupsTable: React.FC<{}> = () => { {GROUP_TABLE_HEADER} {SOURCES_TABLE_HEADER} - {!isFederatedAuth && {USERS_TABLE_HEADER}} + {USERS_TABLE_HEADER} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.test.tsx index 83a20efe5257e..f0758831e2af6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.test.tsx @@ -21,7 +21,7 @@ const setFilterValue = jest.fn(); describe('TableFilters', () => { beforeEach(() => { - setMockValues({ filterValue: '', isFederatedAuth: true }); + setMockValues({ filterValue: '' }); setMockActions({ setFilterValue }); }); it('renders', () => { @@ -29,13 +29,6 @@ describe('TableFilters', () => { expect(wrapper.find(EuiFieldSearch)).toHaveLength(1); expect(wrapper.find(TableFilterSourcesDropdown)).toHaveLength(1); - expect(wrapper.find(TableFilterUsersDropdown)).toHaveLength(0); - }); - - it('renders for non-federated Auth', () => { - setMockValues({ filterValue: '', isFederatedAuth: false }); - const wrapper = shallow(); - expect(wrapper.find(TableFilterUsersDropdown)).toHaveLength(1); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.tsx index e9ea6a7c6b4aa..0907e0b8b3740 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/table_filters.tsx @@ -12,7 +12,6 @@ import { useActions, useValues } from 'kea'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AppLogic } from '../../../app_logic'; import { GroupsLogic } from '../groups_logic'; import { TableFilterSourcesDropdown } from './table_filter_sources_dropdown'; @@ -28,7 +27,6 @@ const FILTER_GROUPS_PLACEHOLDER = i18n.translate( export const TableFilters: React.FC = () => { const { setFilterValue } = useActions(GroupsLogic); const { filterValue } = useValues(GroupsLogic); - const { isFederatedAuth } = useValues(AppLogic); const handleSearchChange = (e: ChangeEvent) => setFilterValue(e.target.value); @@ -47,11 +45,9 @@ export const TableFilters: React.FC = () => { - {!isFederatedAuth && ( - - - - )} + + +
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx index 5be61da22fda9..dccb1ad114db9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx @@ -50,7 +50,6 @@ const mockValues = { filteredSources: [], filteredUsers: [], filterValue: '', - isFederatedAuth: false, }; describe('GroupOverview', () => { @@ -112,12 +111,7 @@ describe('GroupOverview', () => { expect(wrapper.find(ClearFiltersLink)).toHaveLength(1); }); - it('renders inviteUsersButton when not federated auth', () => { - setMockValues({ - ...mockValues, - isFederatedAuth: false, - }); - + it('renders inviteUsersButton', () => { const wrapper = shallow(); const actions = getPageHeaderActions(wrapper); @@ -125,18 +119,6 @@ describe('GroupOverview', () => { expect(actions.find(EuiButtonTo)).toHaveLength(1); }); - it('does not render inviteUsersButton when federated auth', () => { - setMockValues({ - ...mockValues, - isFederatedAuth: true, - }); - - const wrapper = shallow(); - const actions = getPageHeaderActions(wrapper); - - expect(actions.find('[data-test-subj="InviteUsersButton"]')).toHaveLength(0); - }); - it('renders EuiLoadingSpinner when loading', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index 60806a53deb5f..1a4c4f51e93ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -14,10 +14,9 @@ import { i18n } from '@kbn/i18n'; import { FlashMessagesLogic } from '../../../shared/flash_messages'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; -import { AppLogic } from '../../app_logic'; import { WorkplaceSearchPageTemplate } from '../../components/layout'; import { NAV } from '../../constants'; -import { getGroupPath, USERS_PATH } from '../../routes'; +import { getGroupPath, USERS_AND_ROLES_PATH } from '../../routes'; import { AddGroupModal } from './components/add_group_modal'; import { ClearFiltersLink } from './components/clear_filters_link'; @@ -43,8 +42,6 @@ export const Groups: React.FC = () => { filterValue, } = useValues(GroupsLogic); - const { isFederatedAuth } = useValues(AppLogic); - const hasMessages = messages.length > 0; useEffect(() => { @@ -68,7 +65,7 @@ export const Groups: React.FC = () => { const clearFilters = hasFiltersSet && ; const inviteUsersButton = ( - + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action', { defaultMessage: 'Invite users', })} @@ -81,9 +78,7 @@ export const Groups: React.FC = () => { })} ); - const headerActions = !isFederatedAuth - ? [inviteUsersButton, createGroupButton] - : [createGroupButton]; + const headerActions = [inviteUsersButton, createGroupButton]; const noResults = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts index 9aaa6253ea0ab..f6468aefa4fb9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts @@ -27,7 +27,7 @@ export const mockActions = { initializeOverview: jest.fn(() => ({})), }; -const mockValues = { ...mockOverviewValues, ...mockAppValues, isFederatedAuth: true }; +const mockValues = { ...mockOverviewValues, ...mockAppValues }; setMockActions({ ...mockActions }); setMockKeaValues({ ...mockValues }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx index 01e5245c597eb..fd3e1877fdb3e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SOURCES_PATH, USERS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_AND_ROLES_PATH } from '../../routes'; import { OnboardingCard } from './onboarding_card'; import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; @@ -33,9 +33,9 @@ describe('OnboardingSteps', () => { setMockValues({ canCreateContentSources: true }); const wrapper = shallow(); - expect(wrapper.find(OnboardingCard)).toHaveLength(1); - expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(SOURCES_PATH); - expect(wrapper.find(OnboardingCard).prop('description')).toBe( + expect(wrapper.find(OnboardingCard)).toHaveLength(2); + expect(wrapper.find(OnboardingCard).first().prop('actionPath')).toBe(SOURCES_PATH); + expect(wrapper.find(OnboardingCard).first().prop('description')).toBe( 'Add shared sources for your organization to start searching.' ); }); @@ -44,7 +44,7 @@ describe('OnboardingSteps', () => { setMockValues({ sourcesCount: 2, hasOrgSources: true }); const wrapper = shallow(); - expect(wrapper.find(OnboardingCard).prop('description')).toEqual( + expect(wrapper.find(OnboardingCard).first().prop('description')).toEqual( 'You have added 2 shared sources. Happy searching.' ); }); @@ -53,14 +53,13 @@ describe('OnboardingSteps', () => { setMockValues({ canCreateContentSources: false }); const wrapper = shallow(); - expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(undefined); + expect(wrapper.find(OnboardingCard).first().prop('actionPath')).toBe(undefined); }); }); describe('Users & Invitations', () => { - it('renders 0 users when not on federated auth', () => { + it('renders 0 users state', () => { setMockValues({ - isFederatedAuth: false, account, accountsCount: 0, hasUsers: false, @@ -68,7 +67,7 @@ describe('OnboardingSteps', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(2); - expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(USERS_PATH); + expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(USERS_AND_ROLES_PATH); expect(wrapper.find(OnboardingCard).last().prop('description')).toEqual( 'Invite your colleagues into this organization to search with you.' ); @@ -76,7 +75,6 @@ describe('OnboardingSteps', () => { it('renders completed users state', () => { setMockValues({ - isFederatedAuth: false, account, accountsCount: 1, hasUsers: true, @@ -90,7 +88,6 @@ describe('OnboardingSteps', () => { it('disables link when the user cannot create invitations', () => { setMockValues({ - isFederatedAuth: false, account: { ...account, canCreateInvitations: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index a89e3294597e4..44d09ed73ed1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -26,7 +26,7 @@ import { TelemetryLogic } from '../../../shared/telemetry'; import { AppLogic } from '../../app_logic'; import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { ContentSection } from '../../components/shared/content_section'; -import { SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_AND_ROLES_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { OnboardingCard } from './onboarding_card'; import { OverviewLogic } from './overview_logic'; @@ -58,7 +58,6 @@ const ONBOARDING_USERS_CARD_DESCRIPTION = i18n.translate( export const OnboardingSteps: React.FC = () => { const { - isFederatedAuth, organization: { name, defaultOrgName }, account: { isCurated, canCreateInvitations }, } = useValues(AppLogic); @@ -71,8 +70,7 @@ export const OnboardingSteps: React.FC = () => { sourcesCount, } = useValues(OverviewLogic); - const accountsPath = - !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; + const accountsPath = canCreateInvitations || isCurated ? USERS_AND_ROLES_PATH : undefined; const sourcesPath = canCreateContentSources || isCurated ? SOURCES_PATH : undefined; const SOURCES_CARD_DESCRIPTION = i18n.translate( @@ -86,7 +84,7 @@ export const OnboardingSteps: React.FC = () => { return ( - + { actionPath={sourcesPath} complete={hasOrgSources} /> - {!isFederatedAuth && ( - 0 ? 'more' : '' }, - } - )} - actionPath={accountsPath} - complete={hasUsers} - /> - )} + 0 ? 'more' : '' }, + } + )} + actionPath={accountsPath} + complete={hasUsers} + /> {name === defaultOrgName && ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.test.tsx index 110557ac4087a..0d690e500f9e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { setMockValues } from './__mocks__'; import './__mocks__/overview_logic.mock'; import React from 'react'; @@ -21,14 +20,6 @@ describe('OrganizationStats', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(StatisticCard)).toHaveLength(2); - expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(2); - }); - - it('renders additional cards for federated auth', () => { - setMockValues({ isFederatedAuth: false }); - const wrapper = shallow(); - expect(wrapper.find(StatisticCard)).toHaveLength(4); expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(4); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx index d1f0f6a030421..5608032f8bdd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx @@ -13,16 +13,13 @@ import { EuiFlexGrid, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AppLogic } from '../../app_logic'; import { ContentSection } from '../../components/shared/content_section'; -import { SOURCES_PATH, USERS_PATH } from '../../routes'; +import { SOURCES_PATH, USERS_AND_ROLES_PATH } from '../../routes'; import { OverviewLogic } from './overview_logic'; import { StatisticCard } from './statistic_card'; export const OrganizationStats: React.FC = () => { - const { isFederatedAuth } = useValues(AppLogic); - const { sourcesCount, pendingInvitationsCount, accountsCount, personalSourcesCount } = useValues( OverviewLogic ); @@ -37,7 +34,7 @@ export const OrganizationStats: React.FC = () => { } > - + { count={sourcesCount} actionPath={SOURCES_PATH} /> - {!isFederatedAuth && ( - <> - - - - )} + + { publicUrl: undefined, readOnlyMode: false, ilmEnabled: false, - isFederatedAuth: false, configuredLimits: { appSearch: { engine: { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index 8cce01d1932ee..833419e34ef5f 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -75,7 +75,6 @@ export const callEnterpriseSearchConfigAPI = async ({ publicUrl: stripTrailingSlash(data?.settings?.external_url), readOnlyMode: !!data?.settings?.read_only_mode, ilmEnabled: !!data?.settings?.ilm_enabled, - isFederatedAuth: !!data?.settings?.is_federated_auth, // i.e., not standard auth configuredLimits: { appSearch: { engine: { From cebf16fb538895e8bd4fbbc787d964392bfe9430 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Thu, 24 Jun 2021 16:11:16 -0400 Subject: [PATCH 07/40] [Security Solution][Endpoint][Host Isolation] Remove agent status for non endpoint alerts (#102976) --- .../event_details/alert_summary_view.tsx | 11 +++++-- .../common/utils/endpoint_alert_check.test.ts | 31 +++++++++++++++++++ .../common/utils/endpoint_alert_check.ts | 14 +++++++++ .../side_panel/event_details/index.tsx | 4 +-- .../timeline/body/renderers/constants.tsx | 2 +- .../body/renderers/formatted_field.tsx | 4 +-- 6 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index e229c0c6fae49..9cc0b43f52123 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -37,6 +37,7 @@ import { SummaryView } from './summary_view'; import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; import { LineClamp } from '../line_clamp'; +import { endpointAlertCheck } from '../../utils/endpoint_alert_check'; const StyledEuiDescriptionList = styled(EuiDescriptionList)` padding: 24px 4px 4px; @@ -53,7 +54,7 @@ const fields = [ { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, { id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, { id: 'host.name' }, - { id: 'host.status' }, + { id: 'agent.status' }, { id: 'user.name' }, { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, @@ -178,6 +179,10 @@ const AlertSummaryViewComponent: React.FC<{ timelineId, ]); + const isEndpointAlert = useMemo(() => { + return endpointAlertCheck({ data }); + }, [data]); + const agentId = useMemo(() => { const findAgentId = find({ category: 'agent', field: 'agent.id' }, data)?.values; return findAgentId ? findAgentId[0] : ''; @@ -188,7 +193,7 @@ const AlertSummaryViewComponent: React.FC<{ description: { contextId: timelineId, eventId, - fieldName: 'host.status', + fieldName: 'agent.status', value: agentId, linkValue: undefined, }, @@ -209,7 +214,7 @@ const AlertSummaryViewComponent: React.FC<{ {maybeRule?.note && ( diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts new file mode 100644 index 0000000000000..b085fe67d3814 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +import { mockDetailItemData } from '../mock'; +import { endpointAlertCheck } from './endpoint_alert_check'; + +describe('utils', () => { + describe('endpointAlertCheck', () => { + it('should return false if detections data does not come from endpoint rule', () => { + expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy(); + }); + it('should return true if detections data comes from an endpoint rule', () => { + _.remove(mockDetailItemData, function (o) { + return o.field === 'agent.type'; + }); + const mockEndpointDetailItemData = _.concat(mockDetailItemData, { + field: 'agent.type', + originalValue: 'endpoint', + values: ['endpoint'], + isObjectArray: false, + }); + + expect(endpointAlertCheck({ data: mockEndpointDetailItemData })).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts new file mode 100644 index 0000000000000..e399cec0f3bbe --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { find } from 'lodash/fp'; +import { TimelineEventsDetailsItem } from '../../../common/search_strategy'; + +export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] | null }) => { + const findEndpointAlert = find({ field: 'agent.type' }, data)?.values; + return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 76341055f28ef..395538610f567 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -32,6 +32,7 @@ import { } from '../../../../detections/components/host_isolation/translations'; import { ALERT_DETAILS } from './translations'; import { useIsolationPrivileges } from '../../../../common/hooks/endpoint/use_isolate_privileges'; +import { endpointAlertCheck } from '../../../../common/utils/endpoint_alert_check'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflow { @@ -92,8 +93,7 @@ const EventDetailsPanelComponent: React.FC = ({ const isAlert = some({ category: 'signal', field: 'signal.rule.id' }, detailsData); const isEndpointAlert = useMemo(() => { - const findEndpointAlert = find({ category: 'agent', field: 'agent.type' }, detailsData)?.values; - return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false; + return endpointAlertCheck({ data: detailsData }); }, [detailsData]); const agentId = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx index 761d82b482af2..aeb40bed26c8e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx @@ -16,4 +16,4 @@ export const REFERENCE_URL_FIELD_NAME = 'reference.url'; export const EVENT_URL_FIELD_NAME = 'event.url'; export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name'; export const SIGNAL_STATUS_FIELD_NAME = 'signal.status'; -export const HOST_STATUS_FIELD_NAME = 'host.status'; +export const AGENT_STATUS_FIELD_NAME = 'agent.status'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index efb51916e3765..3d5d410abb87e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -32,7 +32,7 @@ import { REFERENCE_URL_FIELD_NAME, EVENT_URL_FIELD_NAME, SIGNAL_STATUS_FIELD_NAME, - HOST_STATUS_FIELD_NAME, + AGENT_STATUS_FIELD_NAME, GEO_FIELD_TYPE, } from './constants'; import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers'; @@ -120,7 +120,7 @@ const FormattedFieldValueComponent: React.FC<{ return ( ); - } else if (fieldName === HOST_STATUS_FIELD_NAME) { + } else if (fieldName === AGENT_STATUS_FIELD_NAME) { return ( Date: Thu, 24 Jun 2021 14:17:09 -0600 Subject: [PATCH 08/40] [Maps] Disable draw mode on layer remove (#103188) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/maps/public/actions/layer_actions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 8bd79474e7f71..1d239a75d1499 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -11,6 +11,7 @@ import { Query } from 'src/plugins/data/public'; import { MapStoreState } from '../reducers/store'; import { createLayerInstance, + getEditState, getLayerById, getLayerList, getLayerListRaw, @@ -481,6 +482,11 @@ function removeLayerFromLayerList(layerId: string) { type: REMOVE_LAYER, id: layerId, }); + // Clean up draw state if needed + const editState = getEditState(getState()); + if (layerId === editState?.layerId) { + dispatch(setDrawMode(DRAW_MODE.NONE)); + } }; } From e1ef2ea5cd2d13ee7ff09bfc015dc43ef4120002 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 24 Jun 2021 14:18:44 -0600 Subject: [PATCH 09/40] [Maps] Disable edit features if editing already enabled for layer (#103300) --- .../__snapshots__/toc_entry.test.tsx.snap | 5 + .../layer_toc/toc_entry/toc_entry.tsx | 1 + .../toc_entry_actions_popover.test.tsx.snap | 115 ++++++++++++++++++ .../toc_entry_actions_popover.test.tsx | 14 +++ .../toc_entry_actions_popover.tsx | 3 +- 5 files changed, 137 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap index 6310b5507dca5..42618d099ffcf 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap @@ -11,6 +11,7 @@ exports[`TOCEntry is rendered 1`] = ` > { openLayerSettings={this._openLayerPanelWithCheck} isEditButtonDisabled={this.props.isEditButtonDisabled} supportsFitToBounds={this.state.supportsFitToBounds} + editModeActiveForLayer={this.props.editModeActiveForLayer} /> {this._renderQuickActions()} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap index 5068a5dc1ad71..5d1fc8e28f993 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap @@ -115,6 +115,121 @@ exports[`TOCEntryActionsPopover is rendered 1`] = ` `; +exports[`TOCEntryActionsPopover should disable Edit features when edit mode active for layer 1`] = ` + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" +> + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] + } + size="m" + /> + +`; + exports[`TOCEntryActionsPopover should disable fit to data when supportsFitToBounds is false 1`] = ` {}, enablePointEditing: () => {}, openLayerSettings: () => {}, + editModeActiveForLayer: false, }; describe('TOCEntryActionsPopover', () => { @@ -100,4 +101,17 @@ describe('TOCEntryActionsPopover', () => { expect(component).toMatchSnapshot(); }); + + test('should disable Edit features when edit mode active for layer', async () => { + const component = shallow( + + ); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index ab7a54be37404..83b4d2c2a756b 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -36,6 +36,7 @@ export interface Props { removeLayer: (layerId: string) => void; supportsFitToBounds: boolean; toggleVisible: (layerId: string) => void; + editModeActiveForLayer: boolean; } interface State { @@ -170,7 +171,7 @@ export class TOCEntryActionsPopover extends Component { defaultMessage: 'Edit features only supported for document layers without clustering, joins, or time filtering', }), - disabled: !this.state.isFeatureEditingEnabled, + disabled: !this.state.isFeatureEditingEnabled || this.props.editModeActiveForLayer, onClick: async () => { this._closePopover(); const supportedShapeTypes = await (this.props.layer.getSource() as ESSearchSource).getSupportedShapeTypes(); From 60086a9aac1fe2fbaa4211210bcb9b3446c1f83a Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 24 Jun 2021 13:30:02 -0700 Subject: [PATCH 10/40] Fix Engine Overview not properly stretching to full page height (#103337) - Caused by the wrapping
around the child views - removing that div and moving the `data-test-subj` hooks to the individual views fixes the issue --- .../components/engine_overview/engine_overview.test.tsx | 5 ----- .../components/engine_overview/engine_overview.tsx | 6 +----- .../components/engine_overview/engine_overview_empty.tsx | 1 + .../components/engine_overview/engine_overview_metrics.tsx | 1 + 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx index a2e0ba4fcd44d..01472987e4d48 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx @@ -29,11 +29,6 @@ describe('EngineOverview', () => { setMockValues(values); }); - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineOverview"]')).toHaveLength(1); - }); - describe('EmptyEngineOverview', () => { it('renders when the engine has no documents & the user can add documents', () => { const myRole = { canManageEngineDocuments: true, canViewEngineCredentials: true }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx index a3f98d8c13e8e..e966709dc1084 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -25,9 +25,5 @@ export const EngineOverview: React.FC = () => { const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials; const showEngineOverview = !isEngineEmpty || !canAddDocuments || isMetaEngine; - return ( -
- {showEngineOverview ? : } -
- ); + return showEngineOverview ? : ; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index 27d9c3723f126..6f8332e1e332e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -34,6 +34,7 @@ export const EmptyEngineOverview: React.FC = () => { , ], }} + data-test-subj="EngineOverview" > diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx index 3cc7138623735..9c3a900dfe115 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx @@ -36,6 +36,7 @@ export const EngineOverviewMetrics: React.FC = () => { }), }} isLoading={dataLoading} + data-test-subj="EngineOverview" > From d5f68eef4f391a2cf9efcf57e258bbf594cf5f0a Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Thu, 24 Jun 2021 16:46:50 -0400 Subject: [PATCH 11/40] [Lens] Fix formula formatting in Metric visualization type (#103167) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../metric_visualization/expression.test.tsx | 98 +++++++++++++++---- .../metric_visualization/expression.tsx | 2 +- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx index 2e189e094ef01..b31125a1912ef 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx @@ -20,17 +20,22 @@ function sampleArgs() { l1: { type: 'datatable', columns: [ - { id: 'a', name: 'a', meta: { type: 'string' } }, + // Simulating a calculated column like a formula + { id: 'a', name: 'a', meta: { type: 'string', params: { id: 'string' } } }, { id: 'b', name: 'b', meta: { type: 'string' } }, - { id: 'c', name: 'c', meta: { type: 'number' } }, + { + id: 'c', + name: 'c', + meta: { type: 'number', params: { id: 'percent', params: { format: '0.000%' } } }, + }, ], - rows: [{ a: 10110, b: 2, c: 3 }], + rows: [{ a: 'last', b: 'last', c: 3 }], }, }, }; const args: MetricConfig = { - accessor: 'a', + accessor: 'c', layerId: 'l1', title: 'My fanci metric chart', description: 'Fancy chart description', @@ -39,7 +44,7 @@ function sampleArgs() { }; const noAttributesArgs: MetricConfig = { - accessor: 'a', + accessor: 'c', layerId: 'l1', title: '', description: '', @@ -65,11 +70,17 @@ describe('metric_expression', () => { }); describe('MetricChart component', () => { - test('it renders the all attributes when passed (title, description, metricTitle, value)', () => { + test('it renders all attributes when passed (title, description, metricTitle, value)', () => { const { data, args } = sampleArgs(); expect( - shallow( x as IFieldFormat} />) + shallow( + ({ convert: (x) => x } as IFieldFormat)} + /> + ) ).toMatchInlineSnapshot(` { } } > - 10110 + 3 +
+
+ My fanci metric chart +
+ + + `); + }); + + test('it renders strings', () => { + const { data, args } = sampleArgs(); + args.accessor = 'a'; + + expect( + shallow( + ({ convert: (x) => x } as IFieldFormat)} + /> + ) + ).toMatchInlineSnapshot(` + + +
+ last
{ x as IFieldFormat} + formatFactory={() => ({ convert: (x) => x } as IFieldFormat)} /> ) ).toMatchInlineSnapshot(` @@ -130,7 +186,7 @@ describe('metric_expression', () => { } } > - 10110 + 3
{ x as IFieldFormat} + formatFactory={() => ({ convert: (x) => x } as IFieldFormat)} /> ) ).toMatchInlineSnapshot(` @@ -174,7 +230,7 @@ describe('metric_expression', () => { } } > - 10110 + 3
@@ -189,7 +245,7 @@ describe('metric_expression', () => { x as IFieldFormat} + formatFactory={() => ({ convert: (x) => x } as IFieldFormat)} /> ) ).toMatchInlineSnapshot(` @@ -202,14 +258,14 @@ describe('metric_expression', () => { test('it renders an EmptyPlaceholder when null value is passed as data', () => { const { data, noAttributesArgs } = sampleArgs(); - data.tables.l1.rows[0].a = null; + data.tables.l1.rows[0].c = null; expect( shallow( x as IFieldFormat} + formatFactory={() => ({ convert: (x) => x } as IFieldFormat)} /> ) ).toMatchInlineSnapshot(` @@ -222,14 +278,14 @@ describe('metric_expression', () => { test('it renders 0 value', () => { const { data, noAttributesArgs } = sampleArgs(); - data.tables.l1.rows[0].a = 0; + data.tables.l1.rows[0].c = 0; expect( shallow( x as IFieldFormat} + formatFactory={() => ({ convert: (x) => x } as IFieldFormat)} /> ) ).toMatchInlineSnapshot(` @@ -264,5 +320,13 @@ describe('metric_expression', () => { `); }); + + test('it finds the right column to format', () => { + const { data, args } = sampleArgs(); + const factory = jest.fn(() => ({ convert: (x) => x } as IFieldFormat)); + + shallow(); + expect(factory).toHaveBeenCalledWith({ id: 'percent', params: { format: '0.000%' } }); + }); }); }); diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.tsx index 70b2cb17c7fe1..60d9d66bce995 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.tsx @@ -127,7 +127,7 @@ export function MetricChart({ return ; } - const column = firstTable.columns[0]; + const column = firstTable.columns.find(({ id }) => id === accessor)!; const row = firstTable.rows[0]; // NOTE: Cardinality and Sum never receives "null" as value, but always 0, even for empty dataset. From b12095b079d7836e554ab0b15702574398fe4149 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 24 Jun 2021 14:13:15 -0700 Subject: [PATCH 12/40] [Metrics UI] Prevent saved views from trampling URL state (#103146) * [Metrics UI] Prevent saved views from trampling URL state * Adding space back in --- .../public/containers/saved_view/saved_view.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx index 56a2a13e31ff7..c54a2a69a994c 100644 --- a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx +++ b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx @@ -255,12 +255,21 @@ export const useSavedView = (props: Props) => { }, [urlState, setUrlState, currentView, defaultViewId, data]); useEffect(() => { - if (!currentView && !loading && data) { + if (!currentView && !loading && data && shouldLoadDefault) { const viewToSet = views.find((v) => v.id === urlState.viewId); if (viewToSet) setCurrentView(viewToSet); else loadDefaultViewIfSet(); } - }, [loading, currentView, data, views, setCurrentView, loadDefaultViewIfSet, urlState.viewId]); + }, [ + loading, + currentView, + data, + views, + setCurrentView, + loadDefaultViewIfSet, + urlState.viewId, + shouldLoadDefault, + ]); return { views, From 45b660172ae868c4e6ddce6e3f88c9ba7e374f87 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 24 Jun 2021 16:19:01 -0500 Subject: [PATCH 13/40] skip suite failing es promotion. #103364 --- .../apis/management/index_lifecycle_management/policies.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js index 756fda7566843..d59d364c38827 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js @@ -32,7 +32,8 @@ export default function ({ getService }) { const { addPolicyToIndex } = registerIndexHelpers({ supertest }); - describe('policies', () => { + // failing ES promotion https://github.com/elastic/kibana/issues/103364 + describe.skip('policies', () => { after(() => Promise.all([cleanUpEsResources(), cleanUpPolicies()])); describe('list', () => { From c1ced880bc3b6d3cf819576e0001d46380fb7495 Mon Sep 17 00:00:00 2001 From: Ross Wolf <31489089+rw-access@users.noreply.github.com> Date: Thu, 24 Jun 2021 15:31:25 -0600 Subject: [PATCH 14/40] [Detections] Adds automatic updating for Prebuilt Security Detection Rules package (#101846) * Automatically install and update the security_detection_engine package * Remove security_detection_engine from required Fleet packages * Update fleet package-registry image * Add sha256: to the distribution package * Use distribution from https://beats-ci.elastic.co/job/Ingest-manager/job/release-distribution/152 * Change fleet required packag * Fix bad merge * Update rules to 0.13.1 package * Fix NOTICE.txt --- NOTICE.txt | 18 ++-- .../public/app/home/index.tsx | 4 +- ...de.ts => use_upgrade_security_packages.ts} | 20 ++--- .../apm_403_response_to_a_post.json | 2 +- .../apm_405_response_method_not_allowed.json | 2 +- .../apm_null_user_agent.json | 2 +- .../apm_sqlmap_user_agent.json | 2 +- ...tion_added_to_google_workspace_domain.json | 2 +- ...tempt_to_deactivate_okta_network_zone.json | 2 +- .../attempt_to_delete_okta_network_zone.json | 2 +- ...collection_cloudtrail_logging_created.json | 2 +- ...ion_gcp_pub_sub_subscription_creation.json | 2 +- ...collection_gcp_pub_sub_topic_creation.json | 2 +- ...llection_microsoft_365_new_inbox_rule.json | 2 +- ...collection_update_event_hub_auth_rule.json | 2 +- ...d_control_certutil_network_connection.json | 7 +- ...mand_and_control_cobalt_strike_beacon.json | 2 +- ...cobalt_strike_default_teamserver_cert.json | 2 +- ..._control_dns_directly_to_the_internet.json | 7 +- ...nd_and_control_dns_tunneling_nslookup.json | 2 +- ...download_rar_powershell_from_internet.json | 7 +- .../command_and_control_fin7_c2_behavior.json | 2 +- .../command_and_control_halfbaked_beacon.json | 2 +- ...d_control_nat_traversal_port_activity.json | 2 +- .../command_and_control_port_26_activity.json | 2 +- ...te_desktop_protocol_from_the_internet.json | 7 +- ...mand_and_control_telnet_port_activity.json | 2 +- ...l_network_computing_from_the_internet.json | 7 +- ...ual_network_computing_to_the_internet.json | 7 +- ...l_access_attempted_bypass_of_okta_mfa.json | 2 +- ...mpts_to_brute_force_okta_user_account.json | 10 ++- ...ccess_aws_iam_assume_role_brute_force.json | 2 +- ...ial_access_collection_sensitive_files.json | 2 +- ...dential_access_dumping_hashes_bi_cmds.json | 2 +- ...ial_access_iam_user_addition_to_group.json | 2 +- .../credential_access_kerberosdump_kcc.json | 2 +- .../credential_access_key_vault_modified.json | 2 +- ..._365_brute_force_user_account_attempt.json | 4 +- ...65_potential_password_spraying_attack.json | 2 +- ...ential_access_mitm_localhost_webproxy.json | 2 +- ...okta_brute_force_or_password_spraying.json | 2 +- ...ntial_access_potential_ssh_bruteforce.json | 2 +- ...cess_root_console_failure_brute_force.json | 2 +- ..._access_secretsmanager_getsecretvalue.json | 2 +- ...ccess_storage_account_key_regenerated.json | 2 +- .../credential_access_systemkey_dumping.json | 2 +- .../defense_evasion_amsienable_key_mod.json | 57 ++++++++++++ ...vasion_apple_softupdates_modification.json | 2 +- ...evasion_attempt_to_disable_gatekeeper.json | 2 +- ...tempt_to_disable_iptables_or_firewall.json | 2 +- ...ion_attempt_to_disable_syslog_service.json | 2 +- ...e_application_credential_modification.json | 2 +- ...on_azure_diagnostic_settings_deletion.json | 2 +- ...sion_azure_service_principal_addition.json | 2 +- ..._base32_encoding_or_decoding_activity.json | 2 +- ...vasion_clearing_windows_security_logs.json | 2 +- ...se_evasion_cloudtrail_logging_deleted.json | 2 +- ..._evasion_cloudtrail_logging_suspended.json | 2 +- ...nse_evasion_cloudwatch_alarm_deletion.json | 2 +- ..._evasion_config_service_rule_deletion.json | 2 +- ...vasion_configuration_recorder_stopped.json | 2 +- .../defense_evasion_cve_2020_0601.json | 2 +- ...fense_evasion_disable_selinux_attempt.json | 2 +- ...defense_evasion_ec2_flow_log_deletion.json | 2 +- ...ense_evasion_ec2_network_acl_deletion.json | 2 +- .../defense_evasion_event_hub_deletion.json | 2 +- ...fense_evasion_file_deletion_via_shred.json | 2 +- ...defense_evasion_file_mod_writable_dir.json | 2 +- ...ense_evasion_firewall_policy_deletion.json | 2 +- ...nse_evasion_gcp_firewall_rule_created.json | 2 +- ...nse_evasion_gcp_firewall_rule_deleted.json | 2 +- ...se_evasion_gcp_firewall_rule_modified.json | 2 +- ...e_evasion_gcp_logging_bucket_deletion.json | 2 +- ...nse_evasion_gcp_logging_sink_deletion.json | 2 +- ...ion_gcp_pub_sub_subscription_deletion.json | 2 +- ...se_evasion_gcp_pub_sub_topic_deletion.json | 2 +- ...storage_bucket_configuration_modified.json | 2 +- ...p_storage_bucket_permissions_modified.json | 2 +- ...e_evasion_guardduty_detector_deletion.json | 2 +- .../defense_evasion_hidden_file_dir_tmp.json | 2 +- .../defense_evasion_injection_msbuild.json | 2 +- ...ense_evasion_install_root_certificate.json | 2 +- ...defense_evasion_kernel_module_removal.json | 2 +- ...osoft_365_exchange_dlp_policy_removed.json | 2 +- ...change_malware_filter_policy_deletion.json | 2 +- ..._365_exchange_malware_filter_rule_mod.json | 2 +- ...65_exchange_safe_attach_rule_disabled.json | 2 +- ...isc_lolbin_connecting_to_the_internet.json | 7 +- ..._evasion_modify_environment_launchctl.json | 2 +- .../defense_evasion_msxsl_network.json | 7 +- ...ense_evasion_network_watcher_deletion.json | 2 +- ...sion_s3_bucket_configuration_deletion.json | 2 +- .../defense_evasion_safari_config_change.json | 2 +- ...dboxed_office_app_suspicious_zip_file.json | 2 +- ...vasion_stop_process_service_threshold.json | 2 +- ...picious_execution_from_mounted_device.json | 89 +++++++++++++++++++ ...ser_password_reset_or_unlock_attempts.json | 12 +-- ...vasion_tcc_bypass_mounted_apfs_access.json | 2 +- ..._evasion_unload_endpointsecurity_kext.json | 2 +- ...nusual_network_connection_via_dllhost.json | 51 +++++++++++ ...usual_network_connection_via_rundll32.json | 7 +- .../defense_evasion_waf_acl_deletion.json | 2 +- ...asion_waf_rule_or_rule_group_deletion.json | 2 +- .../discovery_blob_container_access_mod.json | 2 +- .../discovery_kernel_module_enumeration.json | 2 +- ...covery_virtual_machine_fingerprinting.json | 2 +- ...d_to_google_workspace_trusted_domains.json | 2 +- .../elastic_endpoint_security.json | 2 +- .../endgame_adversary_behavior_detected.json | 2 +- .../endgame_cred_dumping_detected.json | 2 +- .../endgame_cred_dumping_prevented.json | 2 +- .../endgame_cred_manipulation_detected.json | 2 +- .../endgame_cred_manipulation_prevented.json | 2 +- .../endgame_exploit_detected.json | 2 +- .../endgame_exploit_prevented.json | 2 +- .../endgame_malware_detected.json | 2 +- .../endgame_malware_prevented.json | 2 +- .../endgame_permission_theft_detected.json | 2 +- .../endgame_permission_theft_prevented.json | 2 +- .../endgame_process_injection_detected.json | 2 +- .../endgame_process_injection_prevented.json | 2 +- .../endgame_ransomware_detected.json | 2 +- .../endgame_ransomware_prevented.json | 2 +- ...and_prompt_connecting_to_the_internet.json | 7 +- .../execution_command_virtual_machine.json | 2 +- ...vasion_electron_app_childproc_node_js.json | 2 +- ...le_program_connecting_to_the_internet.json | 7 +- ...ution_installer_spawned_network_event.json | 7 +- ...on_pentest_eggshell_remote_admin_tool.json | 2 +- .../execution_perl_tty_shell.json | 2 +- .../execution_python_tty_shell.json | 2 +- ...er_program_connecting_to_the_internet.json | 7 +- ...ing_osascript_exec_followed_by_netcon.json | 7 +- ...ltration_ec2_snapshot_change_activity.json | 2 +- .../exfiltration_ec2_vm_export_failure.json | 70 +++++++++++++++ ...tration_gcp_logging_sink_modification.json | 2 +- ..._365_exchange_transport_rule_creation.json | 2 +- ...osoft_365_exchange_transport_rule_mod.json | 2 +- .../prepackaged_rules/external_alerts.json | 2 +- .../google_workspace_admin_role_deletion.json | 2 +- ...le_workspace_mfa_enforcement_disabled.json | 2 +- .../google_workspace_policy_modified.json | 2 +- ...pact_attempt_to_revoke_okta_api_token.json | 2 +- ...pact_azure_automation_runbook_deleted.json | 2 +- .../impact_cloudtrail_logging_updated.json | 2 +- .../impact_cloudwatch_log_group_deletion.json | 2 +- ...impact_cloudwatch_log_stream_deletion.json | 2 +- .../impact_ec2_disable_ebs_encryption.json | 2 +- .../impact_gcp_iam_role_deletion.json | 2 +- .../impact_gcp_service_account_deleted.json | 2 +- .../impact_gcp_service_account_disabled.json | 2 +- .../impact_gcp_storage_bucket_deleted.json | 2 +- ...virtual_private_cloud_network_deleted.json | 2 +- ...p_virtual_private_cloud_route_created.json | 2 +- ...p_virtual_private_cloud_route_deleted.json | 2 +- .../impact_iam_deactivate_mfa_device.json | 2 +- .../impact_iam_group_deletion.json | 2 +- .../impact_possible_okta_dos_attack.json | 2 +- .../impact_rds_cluster_deletion.json | 2 +- .../impact_rds_instance_cluster_stoppage.json | 2 +- .../impact_resource_group_deletion.json | 2 +- .../rules/prepackaged_rules/index.ts | 10 ++- ...ure_active_directory_high_risk_signin.json | 2 +- ...re_active_directory_powershell_signin.json | 2 +- ...tack_via_azure_registered_application.json | 2 +- .../initial_access_console_login_root.json | 2 +- ...ial_access_external_guest_user_invite.json | 2 +- ...l_access_gcp_iam_custom_role_creation.json | 2 +- .../initial_access_login_failures.json | 2 +- .../initial_access_login_location.json | 2 +- .../initial_access_login_sessions.json | 2 +- .../initial_access_login_time.json | 2 +- ...5_exchange_anti_phish_policy_deletion.json | 2 +- ...soft_365_exchange_anti_phish_rule_mod.json | 2 +- ...osoft_365_exchange_safelinks_disabled.json | 2 +- .../initial_access_password_recovery.json | 2 +- ...mote_procedure_call_from_the_internet.json | 7 +- ...remote_procedure_call_to_the_internet.json | 7 +- ...file_sharing_activity_to_the_internet.json | 2 +- ...icious_activity_reported_by_okta_user.json | 2 +- ...al_access_unsecure_elasticsearch_node.json | 2 +- .../initial_access_via_system_manager.json | 2 +- ..._access_zoom_meeting_with_no_passcode.json | 2 +- ...ential_access_kerberos_bifrostconsole.json | 2 +- .../lateral_movement_dns_server_overflow.json | 2 +- ...ral_movement_remote_ssh_login_enabled.json | 2 +- ...ment_telnet_network_activity_external.json | 7 +- ...ment_telnet_network_activity_internal.json | 7 +- .../linux_hping_activity.json | 2 +- .../linux_iodine_activity.json | 2 +- .../linux_nping_activity.json | 2 +- ...nux_process_started_in_temp_directory.json | 2 +- .../linux_strace_activity.json | 2 +- ...led_for_google_workspace_organization.json | 2 +- ...exchange_dkim_signing_config_disabled.json | 2 +- ..._teams_custom_app_interaction_allowed.json | 2 +- .../ml_high_count_network_denies.json | 6 +- .../ml_high_count_network_events.json | 6 +- .../ml_linux_anomalous_metadata_process.json | 7 +- .../ml_linux_anomalous_metadata_user.json | 7 +- ...linux_anomalous_network_port_activity.json | 7 +- .../ml_linux_anomalous_process_all_hosts.json | 7 +- .../ml_linux_anomalous_user_name.json | 7 +- .../ml_rare_destination_country.json | 6 +- .../ml_rare_process_by_host_linux.json | 7 +- .../ml_rare_process_by_host_windows.json | 7 +- .../ml_spike_in_traffic_to_a_country.json | 6 +- ...ml_windows_anomalous_metadata_process.json | 7 +- .../ml_windows_anomalous_metadata_user.json | 7 +- ...ml_windows_anomalous_network_activity.json | 7 +- .../ml_windows_anomalous_path_activity.json | 7 +- ...l_windows_anomalous_process_all_hosts.json | 7 +- ...ml_windows_anomalous_process_creation.json | 7 +- .../ml_windows_anomalous_user_name.json | 7 +- .../rules/prepackaged_rules/notice.ts | 18 ++-- ...ttempt_to_deactivate_okta_application.json | 2 +- ...kta_attempt_to_deactivate_okta_policy.json | 2 +- ...ttempt_to_deactivate_okta_policy_rule.json | 2 +- ...ta_attempt_to_delete_okta_application.json | 2 +- .../okta_attempt_to_delete_okta_policy.json | 2 +- ...ta_attempt_to_delete_okta_policy_rule.json | 2 +- ...ta_attempt_to_modify_okta_application.json | 2 +- ...a_attempt_to_modify_okta_network_zone.json | 2 +- .../okta_attempt_to_modify_okta_policy.json | 2 +- ...ta_attempt_to_modify_okta_policy_rule.json | 2 +- ..._or_delete_application_sign_on_policy.json | 2 +- ...threat_detected_by_okta_threatinsight.json | 2 +- ...stence_account_creation_hide_at_logon.json | 2 +- ...tor_privileges_assigned_to_okta_group.json | 2 +- ...inistrator_role_assigned_to_okta_user.json | 2 +- ...ence_attempt_to_create_okta_api_token.json | 2 +- ..._deactivate_mfa_for_okta_user_account.json | 2 +- ...set_mfa_factors_for_okta_user_account.json | 2 +- ...ence_azure_automation_account_created.json | 2 +- ...utomation_runbook_created_or_modified.json | 2 +- ...ence_azure_automation_webhook_created.json | 2 +- ...re_conditional_access_policy_modified.json | 2 +- ...nce_azure_pim_user_added_global_admin.json | 2 +- ...ged_identity_management_role_modified.json | 2 +- ..._access_authorization_plugin_creation.json | 2 +- ...l_access_modify_auth_module_or_config.json | 2 +- ...credential_access_modify_ssh_binaries.json | 2 +- ...launch_agent_deamon_logonitem_process.json | 2 +- ...rectory_services_plugins_modification.json | 2 +- ...e_docker_shortcuts_plist_modification.json | 2 +- .../persistence_ec2_network_acl_creation.json | 2 +- .../persistence_enable_root_account.json | 2 +- ..._gcp_iam_service_account_key_deletion.json | 2 +- ...e_gcp_key_created_for_service_account.json | 2 +- ...rsistence_gcp_service_account_created.json | 2 +- ...workspace_admin_role_assigned_to_user.json | 2 +- ...a_domain_wide_delegation_of_authority.json | 2 +- ...e_workspace_custom_admin_role_created.json | 2 +- ...stence_google_workspace_role_modified.json | 2 +- .../persistence_iam_group_creation.json | 2 +- ...stence_loginwindow_plist_modification.json | 2 +- ...rsistence_mfa_disabled_for_azure_user.json | 2 +- ...5_exchange_management_role_assignment.json | 2 +- ...oft_365_teams_external_access_enabled.json | 2 +- ...rosoft_365_teams_guest_access_enabled.json | 2 +- ...ersistence_periodic_tasks_file_mdofiy.json | 2 +- .../persistence_rds_cluster_creation.json | 2 +- ...sistence_shell_activity_by_web_server.json | 2 +- ...ersistence_shell_profile_modification.json | 2 +- ...ence_ssh_authorized_keys_modification.json | 2 +- ...ence_suspicious_calendar_modification.json | 2 +- ...stence_suspicious_com_hijack_registry.json | 4 +- ..._added_as_owner_for_azure_application.json | 2 +- ..._as_owner_for_azure_service_principal.json | 2 +- ...tence_via_atom_init_file_modification.json | 2 +- ...lege_escalation_echo_nopasswd_sudoers.json | 2 +- ...calation_explicit_creds_via_scripting.json | 2 +- ...alation_exploit_adobe_acrobat_updater.json | 2 +- ...lation_ld_preload_shared_object_modif.json | 2 +- ..._escalation_local_user_added_to_admin.json | 2 +- ...ge_escalation_persistence_phantom_dll.json | 4 +- ...ilege_escalation_root_crontab_filemod.json | 2 +- ...ege_escalation_root_login_without_mfa.json | 2 +- ...ation_setuid_setgid_bit_set_via_chmod.json | 2 +- ...ilege_escalation_sudo_buffer_overflow.json | 2 +- ...privilege_escalation_sudoers_file_mod.json | 2 +- ...ege_escalation_updateassumerolepolicy.json | 2 +- .../threat_intel_module_match.json | 2 +- .../fleet_api_integration/apis/epm/delete.ts | 2 +- x-pack/test/fleet_api_integration/config.ts | 3 +- 285 files changed, 728 insertions(+), 362 deletions(-) rename x-pack/plugins/security_solution/public/common/hooks/{endpoint/upgrade.ts => use_upgrade_security_packages.ts} (80%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json diff --git a/NOTICE.txt b/NOTICE.txt index b0f7e65f46fa9..4ede43610ca7b 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -149,17 +149,17 @@ SOFTWARE. --- Detection Rules -Copyright 2020 Elasticsearch B.V. +Copyright 2021 Elasticsearch B.V. --- This product bundles rules based on https://github.com/BlueTeamLabs/sentinel-attack -which is available under a "MIT" license. The files based on this license are: +which is available under a "MIT" license. The rules based on this license are: -- defense_evasion_via_filter_manager -- discovery_process_discovery_via_tasklist_command -- persistence_priv_escalation_via_accessibility_features -- persistence_via_application_shimming -- defense_evasion_execution_via_trusted_developer_utilities +- "Potential Evasion via Filter Manager" (06dceabf-adca-48af-ac79-ffdf4c3b1e9a) +- "Process Discovery via Tasklist" (cc16f774-59f9-462d-8b98-d27ccd4519ec) +- "Potential Modification of Accessibility Binaries" (7405ddf1-6c8e-41ce-818f-48bea6bcaed8) +- "Potential Application Shimming via Sdbinst" (fd4a992d-6130-4802-9ff8-829b89ae801f) +- "Trusted Developer Application Usage" (9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae1) MIT License @@ -185,9 +185,9 @@ SOFTWARE. --- This product bundles rules based on https://github.com/FSecureLABS/leonidas -which is available under a "MIT" license. The files based on this license are: +which is available under a "MIT" license. The rules based on this license are: -- credential_access_secretsmanager_getsecretvalue.toml +- "AWS Access Secret in Secrets Manager" (a00681e3-9ed6-447c-ab2c-be648821c622) MIT License diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 9a57ab3fc3a73..17a6fab103d6f 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -17,7 +17,7 @@ import { useInitSourcerer, useSourcererScope } from '../../common/containers/sou import { useKibana } from '../../common/lib/kibana'; import { DETECTIONS_SUB_PLUGIN_ID } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; -import { useUpgradeEndpointPackage } from '../../common/hooks/endpoint/upgrade'; +import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_security_packages'; import { GlobalHeader } from './global_header'; import { SecuritySolutionTemplateWrapper } from './template_wrapper'; @@ -56,7 +56,7 @@ const HomePageComponent: React.FC = ({ // tabs in the app. This is useful for keeping the endpoint package as up to date as possible until // a background task solution can be built on the server side. Once a background task solution is available we // can remove this. - useUpgradeEndpointPackage(); + useUpgradeSecurityPackages(); return ( diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts similarity index 80% rename from x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts rename to x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts index 8e083b3c6b5f2..6a3afccd8794d 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts @@ -6,31 +6,31 @@ */ import { useEffect } from 'react'; -import { HttpFetchOptions, HttpStart } from 'src/core/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { HttpFetchOptions, HttpStart } from 'kibana/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { epmRouteService, appRoutesService, CheckPermissionsResponse, BulkInstallPackagesResponse, -} from '../../../../../fleet/common'; -import { StartServices } from '../../../types'; -import { useIngestEnabledCheck } from './ingest_enabled'; +} from '../../../../fleet/common'; +import { StartServices } from '../../types'; +import { useIngestEnabledCheck } from './endpoint/ingest_enabled'; /** - * Requests that the endpoint package be upgraded to the latest version + * Requests that the endpoint and security_detection_engine package be upgraded to the latest version * * @param http an http client for sending the request * @param options an object containing options for the request */ -const sendUpgradeEndpointPackage = async ( +const sendUpgradeSecurityPackages = async ( http: HttpStart, options: HttpFetchOptions = {} ): Promise => { return http.post(epmRouteService.getBulkInstallPath(), { ...options, body: JSON.stringify({ - packages: ['endpoint'], + packages: ['endpoint', 'security_detection_engine'], }), }); }; @@ -51,7 +51,7 @@ const sendCheckPermissions = async ( }); }; -export const useUpgradeEndpointPackage = () => { +export const useUpgradeSecurityPackages = () => { const context = useKibana(); const { allEnabled: ingestEnabled } = useIngestEnabledCheck(); @@ -79,7 +79,7 @@ export const useUpgradeEndpointPackage = () => { } // ignore the response for now since we aren't notifying the user - await sendUpgradeEndpointPackage(context.services.http, { signal }); + await sendUpgradeSecurityPackages(context.services.http, { signal }); } catch (error) { // Ignore Errors, since this should not hinder the user's ability to use the UI diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json index 4c40216391c8b..76ba58be0a428 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Web Application Suspicious Activity: POST Request Declined", - "query": "http.response.status_code:403 and http.request.method:post", + "query": "http.response.status_code:403 and http.request.method:post\n", "references": [ "https://en.wikipedia.org/wiki/HTTP_403" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json index 40138f470c631..0633004273952 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Web Application Suspicious Activity: Unauthorized Method", - "query": "http.response.status_code:405", + "query": "http.response.status_code:405\n", "references": [ "https://en.wikipedia.org/wiki/HTTP_405" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_null_user_agent.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_null_user_agent.json index a2e9b130f0840..87bbfd727cdf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_null_user_agent.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_null_user_agent.json @@ -30,7 +30,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Web Application Suspicious Activity: No User Agent", - "query": "url.path:*", + "query": "url.path:*\n", "references": [ "https://en.wikipedia.org/wiki/User_agent" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json index ec65f7cb57661..334cf78b4338c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Web Application Suspicious Activity: sqlmap User Agent", - "query": "user_agent.original:\"sqlmap/1.3.11#stable (http://sqlmap.org)\"", + "query": "user_agent.original:\"sqlmap/1.3.11#stable (http://sqlmap.org)\"\n", "references": [ "http://sqlmap.org/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json index b447e59e71435..c45d377645b05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Application Added to Google Workspace Domain", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ADD_APPLICATION", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ADD_APPLICATION\n", "references": [ "https://support.google.com/a/answer/6328701?hl=en#" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_deactivate_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_deactivate_okta_network_zone.json index fbdf3d58f2b81..8c8353f1d5f9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_deactivate_okta_network_zone.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_deactivate_okta_network_zone.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Deactivate an Okta Network Zone", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:zone.deactivate", + "query": "event.dataset:okta.system and event.action:zone.deactivate\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_delete_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_delete_okta_network_zone.json index b0bd850c1d63c..903475b441d01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_delete_okta_network_zone.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/attempt_to_delete_okta_network_zone.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Delete an Okta Network Zone", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:zone.delete", + "query": "event.dataset:okta.system and event.action:zone.delete\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json index 3e2479369ebb2..7debf76f371c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudTrail Log Created", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:CreateTrail and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:CreateTrail and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_CreateTrail.html", "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/create-trail.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json index d50013c90b122..a24c533e2c272 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Pub/Sub Subscription Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Subscriber.CreateSubscription and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Subscriber.CreateSubscription and event.outcome:success\n", "references": [ "https://cloud.google.com/pubsub/docs/overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json index 5438b8fb2a8c9..af20bdf46e42a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Pub/Sub Topic Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Publisher.CreateTopic and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Publisher.CreateTopic and event.outcome:success\n", "references": [ "https://cloud.google.com/pubsub/docs/admin" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json index 5fcfbd797e9d3..1a9ccc9c70696 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 New Inbox Rule Created", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-InboxRule\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-InboxRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/responding-to-a-compromised-email-account?view=o365-worldwide", "https://docs.microsoft.com/en-us/powershell/module/exchange/new-inboxrule?view=exchange-ps", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json index a9fd96efcb383..7e8ab8d94d8f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Event Hub Authorization Rule Created or Updated", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.EVENTHUB/NAMESPACES/AUTHORIZATIONRULES/WRITE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.EVENTHUB/NAMESPACES/AUTHORIZATIONRULES/WRITE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/event-hubs/authorize-access-shared-access-signature" ], 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 c6622deed9502..2f265710feb0a 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 @@ -12,7 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Certutil", - "query": "sequence by process.entity_id\n [process where process.name : \"certutil.exe\" and event.type == \"start\"]\n [network where process.name : \"certutil.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")]\n", + "query": "sequence by process.entity_id\n [process where process.name : \"certutil.exe\" and event.type == \"start\"]\n [network where process.name : \"certutil.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "3838e0e3-1850-4850-a411-2e8c5ba40ba8", "severity": "low", @@ -41,5 +44,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json index eba26c7be6e94..4e1780af8df82 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Cobalt Strike Command and Control Beacon", "note": "## Threat intel\n\nThis activity has been observed in FIN7 campaigns.", - "query": "event.category:(network OR network_traffic) AND type:(tls OR http) AND network.transport:tcp AND destination.domain:/[a-z]{3}.stage.[0-9]{8}\\..*/", + "query": "event.category:(network OR network_traffic) AND type:(tls OR http) AND network.transport:tcp AND destination.domain:/[a-z]{3}.stage.[0-9]{8}\\..*/\n", "references": [ "https://blog.morphisec.com/fin7-attacks-restaurant-industry", "https://www.fireeye.com/blog/threat-research/2017/04/fin7-phishing-lnk.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json index 6fffa5c4634a0..1d57f302554b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Default Cobalt Strike Team Server Certificate", "note": "## Threat intel\n\nWhile Cobalt Strike is intended to be used for penetration tests and IR training, it is frequently used by actual threat actors (TA) such as APT19, APT29, APT32, APT41, FIN6, DarkHydrus, CopyKittens, Cobalt Group, Leviathan, and many other unnamed criminal TAs. This rule uses high-confidence atomic indicators, alerts should be investigated rapidly.", - "query": "event.category:(network or network_traffic) and (tls.server.hash.md5:950098276A495286EB2A2556FBAB6D83 or tls.server.hash.sha1:6ECE5ECE4192683D2D84E25B0BA7E04F9CB7EB7C or tls.server.hash.sha256:87F2085C32B6A2CC709B365F55873E207A9CAA10BFFECF2FD16D3CF9D94D390C)", + "query": "event.category:(network or network_traffic) and (tls.server.hash.md5:950098276A495286EB2A2556FBAB6D83 or\n tls.server.hash.sha1:6ECE5ECE4192683D2D84E25B0BA7E04F9CB7EB7C or\n tls.server.hash.sha256:87F2085C32B6A2CC709B365F55873E207A9CAA10BFFECF2FD16D3CF9D94D390C)\n", "references": [ "https://attack.mitre.org/software/S0154/", "https://www.cobaltstrike.com/help-setup-collaboration", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json index d283b489c278d..ec05c1645c699 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json @@ -13,10 +13,11 @@ "language": "kuery", "license": "Elastic License v2", "name": "DNS Activity to the Internet", - "query": "event.category:(network or network_traffic) and (event.type:connection or type:dns) and (destination.port:53 or event.dataset:zeek.dns) and source.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 ) and not destination.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or 255.255.255.255 or \"::1\" or \"FE80::/10\" or \"FF00::/8\")", + "query": "event.category:(network or network_traffic) and (event.type:connection or type:dns) and (destination.port:53 or event.dataset:zeek.dns)\n and source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n", "references": [ "https://www.us-cert.gov/ncas/alerts/TA15-240A", - "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-81-2.pdf" + "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-81-2.pdf", + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" ], "risk_score": 47, "rule_id": "6ea71ff0-9e95-475b-9506-2580d1ce6154", @@ -40,5 +41,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json index 62654dfb2a9dd..0920f336bab44 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential DNS Tunneling via NsLookup", - "query": "event.category:process and event.type:start and process.name:nslookup.exe and process.args:(-querytype=* or -qt=* or -q=* or -type=*)", + "query": "event.category:process and event.type:start and process.name:nslookup.exe and process.args:(-querytype=* or -qt=* or -q=* or -type=*)\n", "references": [ "https://unit42.paloaltonetworks.com/dns-tunneling-in-the-wild-overview-of-oilrigs-dns-tunneling/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json index 7cfba90cf67c8..50010d809f6fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json @@ -13,10 +13,11 @@ "license": "Elastic License v2", "name": "Roshal Archive (RAR) or PowerShell File Downloaded from the Internet", "note": "## Threat intel\n\nThis activity has been observed in FIN7 campaigns.", - "query": "event.category:(network OR network_traffic) AND network.protocol:http AND url.path:/.*(rar|ps1)/ AND source.ip:(10.0.0.0\\/8 OR 172.16.0.0\\/12 OR 192.168.0.0\\/16)", + "query": "event.category:(network or network_traffic) and network.protocol:http and\n (url.extension:(ps1 or rar) or url.path:(*.ps1 or *.rar)) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n ) and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n )\n", "references": [ "https://www.fireeye.com/blog/threat-research/2017/04/fin7-phishing-lnk.html", - "https://www.justice.gov/opa/press-release/file/1084361/download" + "https://www.justice.gov/opa/press-release/file/1084361/download", + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" ], "risk_score": 47, "rule_id": "ff013cb4-274d-434a-96bb-fe15ddd3ae92", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json index 305612da0f47e..80087935eae0f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Possible FIN7 DGA Command and Control Behavior", "note": "## Triage and analysis\n\nIn the event this rule identifies benign domains in your environment, the `destination.domain` field in the rule can be modified to include those domains. Example: `...AND NOT destination.domain:(zoom.us OR benign.domain1 OR benign.domain2)`.", - "query": "event.category:(network OR network_traffic) AND type:(tls OR http) AND network.transport:tcp AND destination.domain:/[a-zA-Z]{4,5}\\.(pw|us|club|info|site|top)/ AND NOT destination.domain:zoom.us", + "query": "event.category:(network OR network_traffic) AND type:(tls OR http) AND network.transport:tcp\nAND destination.domain:/[a-zA-Z]{4,5}\\.(pw|us|club|info|site|top)/ AND NOT destination.domain:zoom.us\n", "references": [ "https://www.fireeye.com/blog/threat-research/2018/08/fin7-pursuing-an-enigmatic-and-evasive-global-criminal-operation.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json index ed6fb726896ac..f7a770b4cfcc9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Halfbaked Command and Control Beacon", "note": "## Threat intel\n\nThis activity has been observed in FIN7 campaigns.", - "query": "event.category:(network OR network_traffic) AND network.protocol:http AND network.transport:tcp AND url.full:/http:\\/\\/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}\\/cd/ AND destination.port:(53 OR 80 OR 8080 OR 443)", + "query": "event.category:(network OR network_traffic) AND network.protocol:http AND\n network.transport:tcp AND url.full:/http:\\/\\/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}\\/cd/ AND\n destination.port:(53 OR 80 OR 8080 OR 443)\n", "references": [ "https://www.fireeye.com/blog/threat-research/2017/04/fin7-phishing-lnk.html", "https://attack.mitre.org/software/S0151/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json index 33b7cb020a69f..7cd0a865c4b3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json @@ -15,7 +15,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "IPSEC NAT Traversal Port Activity", - "query": "event.category:(network or network_traffic) and network.transport:udp and destination.port:4500", + "query": "event.category:(network or network_traffic) and network.transport:udp and destination.port:4500\n", "risk_score": 21, "rule_id": "a9cb3641-ff4b-4cdc-a063-b4b8d02a67c7", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json index 48a7fae4b259e..1ea0da645d785 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json @@ -15,7 +15,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SMTP on Port 26/TCP", - "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:26 or (event.dataset:zeek.smtp and destination.port:26))", + "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:26 or (event.dataset:zeek.smtp and destination.port:26))\n", "references": [ "https://unit42.paloaltonetworks.com/unit42-badpatch/", "https://isc.sans.edu/forums/diary/Next+up+whats+up+with+TCP+port+26/25564/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json index 1ecfdc45352aa..625b1e470192f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json @@ -15,7 +15,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "RDP (Remote Desktop Protocol) from the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:3389 or event.dataset:zeek.rdp) and not source.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" ) and destination.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:3389 or event.dataset:zeek.rdp) and\n not source.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n ) and\n destination.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n )\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 47, "rule_id": "8c1bdde8-4204-45c0-9e0c-c85ca3902488", "severity": "medium", @@ -69,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json index f66e2ce891198..34adaa49df9c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json @@ -15,7 +15,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Telnet Port Activity", - "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port:23", + "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port:23\n", "risk_score": 47, "rule_id": "34fde489-94b0-4500-a76f-b8a157cf9269", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json index e593a2fed8ba0..7a3da39baad33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json @@ -15,7 +15,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "VNC (Virtual Network Computing) from the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port >= 5800 and destination.port <= 5810 and not source.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" ) and destination.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port >= 5800 and destination.port <= 5810 and\n not source.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n ) and\n destination.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n )\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 73, "rule_id": "5700cb81-df44-46aa-a5d7-337798f53eb8", "severity": "high", @@ -60,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json index 2e43ad410daec..4b30de0ee0211 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json @@ -15,7 +15,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "VNC (Virtual Network Computing) to the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port >= 5800 and destination.port <= 5810 and source.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 ) and not destination.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port >= 5800 and destination.port <= 5810 and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 47, "rule_id": "3ad49c61-7adc-42c1-b788-732eda2f5abf", "severity": "medium", @@ -45,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json index c3614e0e69e9a..fc3c3f5ca90d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Attempted Bypass of Okta MFA", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.mfa.attempt_bypass", + "query": "event.dataset:okta.system and event.action:user.mfa.attempt_bypass\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json index 5556f3787fbdd..ea2b05b485585 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json @@ -1,6 +1,8 @@ { "author": [ - "Elastic" + "Elastic", + "@BenB196", + "Austin Songer" ], "description": "Identifies when an Okta user account is locked out 3 times within a 3 hour window. An adversary may attempt a brute force or password spraying attack to obtain unauthorized access to user accounts. The default Okta authentication policy ensures that a user account is locked out after 10 failed authentication attempts.", "from": "now-180m", @@ -12,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempts to Brute Force an Okta User Account", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.account.lock", + "query": "event.dataset:okta.system and event.action:user.account.lock\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" @@ -47,10 +49,10 @@ ], "threshold": { "field": [ - "okta.actor.id" + "okta.actor.alternate_id" ], "value": 3 }, "type": "threshold", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json index 3ea689aaa3570..6ef3e3d255779 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "AWS IAM Brute Force of Assume Role Policy", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:UpdateAssumeRolePolicy and aws.cloudtrail.error_code:MalformedPolicyDocumentException and event.outcome:failure", + "query": "event.dataset:aws.cloudtrail and\n event.provider:iam.amazonaws.com and event.action:UpdateAssumeRolePolicy and\n aws.cloudtrail.error_code:MalformedPolicyDocumentException and event.outcome:failure\n", "references": [ "https://www.praetorian.com/blog/aws-iam-assume-role-vulnerabilities", "https://rhinosecuritylabs.com/aws/assume-worst-aws-assume-role-enumeration/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json index b8a608dc47ab1..bd155ce8e914d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Sensitive Files Compression", - "query": "event.category:process and event.type:start and process.name:(zip or tar or gzip or hdiutil or 7z) and process.args: ( /root/.ssh/id_rsa or /root/.ssh/id_rsa.pub or /root/.ssh/id_ed25519 or /root/.ssh/id_ed25519.pub or /root/.ssh/authorized_keys or /root/.ssh/authorized_keys2 or /root/.ssh/known_hosts or /root/.bash_history or /etc/hosts or /home/*/.ssh/id_rsa or /home/*/.ssh/id_rsa.pub or /home/*/.ssh/id_ed25519 or /home/*/.ssh/id_ed25519.pub or /home/*/.ssh/authorized_keys or /home/*/.ssh/authorized_keys2 or /home/*/.ssh/known_hosts or /home/*/.bash_history or /root/.aws/credentials or /root/.aws/config or /home/*/.aws/credentials or /home/*/.aws/config or /root/.docker/config.json or /home/*/.docker/config.json or /etc/group or /etc/passwd or /etc/shadow or /etc/gshadow )", + "query": "event.category:process and event.type:start and\n process.name:(zip or tar or gzip or hdiutil or 7z) and\n process.args:\n (\n /root/.ssh/id_rsa or\n /root/.ssh/id_rsa.pub or\n /root/.ssh/id_ed25519 or\n /root/.ssh/id_ed25519.pub or\n /root/.ssh/authorized_keys or\n /root/.ssh/authorized_keys2 or\n /root/.ssh/known_hosts or\n /root/.bash_history or\n /etc/hosts or\n /home/*/.ssh/id_rsa or\n /home/*/.ssh/id_rsa.pub or\n /home/*/.ssh/id_ed25519 or\n /home/*/.ssh/id_ed25519.pub or\n /home/*/.ssh/authorized_keys or\n /home/*/.ssh/authorized_keys2 or\n /home/*/.ssh/known_hosts or\n /home/*/.bash_history or\n /root/.aws/credentials or\n /root/.aws/config or\n /home/*/.aws/credentials or\n /home/*/.aws/config or\n /root/.docker/config.json or\n /home/*/.docker/config.json or\n /etc/group or\n /etc/passwd or\n /etc/shadow or\n /etc/gshadow\n )\n", "references": [ "https://www.trendmicro.com/en_ca/research/20/l/teamtnt-now-deploying-ddos-capable-irc-bot-tntbotinger.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json index c9070cc09dd65..bb513cbebdc3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Dumping Account Hashes via Built-In Commands", - "query": "event.category:process and event.type:start and process.name:(defaults or mkpassdb) and process.args:(ShadowHashData or \"-dump\")", + "query": "event.category:process and event.type:start and\n process.name:(defaults or mkpassdb) and process.args:(ShadowHashData or \"-dump\")\n", "references": [ "https://apple.stackexchange.com/questions/186893/os-x-10-9-where-are-password-hashes-stored", "https://www.unix.com/man-page/osx/8/mkpassdb/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json index 0d94ac36944a9..b66008c6931a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS IAM User Addition to Group", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:AddUserToGroup and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:AddUserToGroup and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/IAM/latest/APIReference/API_AddUserToGroup.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json index 9429e3414e615..de5a9d80ed3df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Kerberos Cached Credentials Dumping", - "query": "event.category:process and event.type:(start or process_started) and process.name:kcc and process.args:copy_cred_cache", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:kcc and\n process.args:copy_cred_cache\n", "references": [ "https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/kerberosdump.py", "https://opensource.apple.com/source/Heimdal/Heimdal-323.12/kuser/kcc-commands.in.auto.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json index 27f76a13d657d..8efd74d4e7f1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Key Vault Modified", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.KEYVAULT/VAULTS/WRITE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.KEYVAULT/VAULTS/WRITE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/key-vault/general/basic-concepts", "https://docs.microsoft.com/en-us/azure/key-vault/general/secure-your-key-vault" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json index 8e9c142929a05..532b9bf3b17b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Attempts to Brute Force a Microsoft 365 User Account", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:AzureActiveDirectory and event.category:authentication and event.action:UserLoginFailed and event.outcome:failure", + "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:failure\n", "risk_score": 73, "rule_id": "26f68dba-ce29-497b-8e13-b4fde1db5a2d", "severity": "high", @@ -51,5 +51,5 @@ "value": 10 }, "type": "threshold", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json index 14314feebb712..536f893236dee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Potential Password Spraying of Microsoft 365 User Accounts", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:AzureActiveDirectory and event.category:authentication and event.action:UserLoginFailed and event.outcome:failure", + "query": "event.dataset:o365.audit and event.provider:AzureActiveDirectory and event.category:authentication and event.action:UserLoginFailed and event.outcome:failure\n", "risk_score": 73, "rule_id": "3efee4f0-182a-40a8-a835-102c68a4175d", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json index e226df7a23da9..9c0f2d0c07dc9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "WebProxy Settings Modification", - "query": "event.category : process and event.type : start and process.name : networksetup and process.args : ((\"-setwebproxy\" or \"-setsecurewebproxy\" or \"-setautoproxyurl\") and not (Bluetooth or off)) and not process.parent.executable : (\"/Library/PrivilegedHelperTools/com.80pct.FreedomHelper\" or \"/Applications/Fiddler Everywhere.app/Contents/Resources/app/out/WebServer/Fiddler.WebUi\" or \"/usr/libexec/xpcproxy\")", + "query": "event.category : process and event.type : start and\n process.name : networksetup and process.args : ((\"-setwebproxy\" or \"-setsecurewebproxy\" or \"-setautoproxyurl\") and not (Bluetooth or off)) and\n not process.parent.executable : (\"/Library/PrivilegedHelperTools/com.80pct.FreedomHelper\" or\n \"/Applications/Fiddler Everywhere.app/Contents/Resources/app/out/WebServer/Fiddler.WebUi\" or\n \"/usr/libexec/xpcproxy\")\n", "references": [ "https://unit42.paloaltonetworks.com/mac-malware-steals-cryptocurrency-exchanges-cookies/", "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json index 16c2816e30690..f5449e4e30a1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Okta Brute Force or Password Spraying Attack", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.category:authentication and event.outcome:failure", + "query": "event.dataset:okta.system and event.category:authentication and event.outcome:failure\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json index 410db35bf77d3..4f945e22abdb3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_ssh_bruteforce.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential SSH Brute Force Detected", - "query": "event.category:process and event.type:start and process.name:\"sshd-keygen-wrapper\" and process.parent.name:launchd", + "query": "event.category:process and event.type:start and process.name:\"sshd-keygen-wrapper\" and process.parent.name:launchd\n", "references": [ "https://themittenmac.com/detecting-ssh-activity-via-process-monitoring/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json index fb296d82b992b..b20ef22a9d152 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "AWS Management Console Brute Force of Root User Identity", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and aws.cloudtrail.user_identity.type:Root and event.outcome:failure", + "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and aws.cloudtrail.user_identity.type:Root and event.outcome:failure\n", "references": [ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json index 8eebcc5c45096..6d0d4c8d112a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "AWS Access Secret in Secrets Manager", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:secretsmanager.amazonaws.com and event.action:GetSecretValue", + "query": "event.dataset:aws.cloudtrail and event.provider:secretsmanager.amazonaws.com and event.action:GetSecretValue\n", "references": [ "https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html", "http://detectioninthe.cloud/credential_access/access_secret_in_secrets_manager/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json index d6f5a05c86e1a..3f286081c49f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Storage Account Key Regenerated", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.STORAGE/STORAGEACCOUNTS/REGENERATEKEY/ACTION\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.STORAGE/STORAGEACCOUNTS/REGENERATEKEY/ACTION\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json index 9094124c480bd..9eaa51279bf4b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SystemKey Access via Command Line", - "query": "event.category:process and event.type:(start or process_started) and process.args:\"/private/var/db/SystemKey\"", + "query": "event.category:process and event.type:(start or process_started) and\n process.args:\"/private/var/db/SystemKey\"\n", "references": [ "https://github.com/AlessandroZ/LaZagne/blob/master/Mac/lazagne/softwares/system/chainbreaker.py" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json new file mode 100644 index 0000000000000..6f30b53d24bdb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Jscript tries to query the AmsiEnable registry key from the HKEY_USERS registry hive before initializing Antimalware Scan Interface (AMSI). If this key is set to 0, AMSI is not enabled for the Jscript process. An adversary can modify this key to disable AMSI protections.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Modification of AmsiEnable Registry Key", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path: \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\" and\n registry.data.strings: \"0\"\n", + "references": [ + "https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf", + "https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal" + ], + "risk_score": 73, + "rule_id": "f874315d-5188-4b4a-8521-d1c73093a7e4", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json index 9340425c518a7..f354f521d168e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SoftwareUpdate Preferences Modification", - "query": "event.category:process and event.type:(start or process_started) and process.name:defaults and process.args:(write and \"-bool\" and (com.apple.SoftwareUpdate or /Library/Preferences/com.apple.SoftwareUpdate.plist) and not (TRUE or true))", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:defaults and \n process.args:(write and \"-bool\" and (com.apple.SoftwareUpdate or /Library/Preferences/com.apple.SoftwareUpdate.plist) and not (TRUE or true))\n", "references": [ "https://blog.checkpoint.com/2017/07/13/osxdok-refuses-go-away-money/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json index ab423dc0ebe14..9bb48361ccf89 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Disable Gatekeeper", - "query": "event.category:process and event.type:(start or process_started) and process.args:(spctl and \"--master-disable\")", + "query": "event.category:process and event.type:(start or process_started) and \n process.args:(spctl and \"--master-disable\")\n", "references": [ "https://support.apple.com/en-us/HT202491", "https://www.carbonblack.com/blog/tau-threat-intelligence-notification-new-macos-malware-variant-of-shlayer-osx-discovered/" 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 8913c63f811dd..3eb5f25298c72 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Disable IPTables or Firewall", - "query": "event.category:process and event.type:(start or process_started) and process.name:ufw and process.args:(allow or disable or reset) or (((process.name:service and process.args:stop) or (process.name:chkconfig and process.args:off) or (process.name:systemctl and process.args:(disable or stop or kill))) and process.args:(firewalld or ip6tables or iptables))", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:ufw and process.args:(allow or disable or reset) or\n\n (((process.name:service and process.args:stop) or\n (process.name:chkconfig and process.args:off) or\n (process.name:systemctl and process.args:(disable or stop or kill))) and\n process.args:(firewalld or ip6tables or iptables))\n", "risk_score": 47, "rule_id": "125417b8-d3df-479f-8418-12d7e034fee3", "severity": "medium", 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 b465340a83223..610648df94d37 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Disable Syslog Service", - "query": "event.category:process and event.type:(start or process_started) and ((process.name:service and process.args:stop) or (process.name:chkconfig and process.args:off) or (process.name:systemctl and process.args:(disable or stop or kill))) and process.args:(syslog or rsyslog or \"syslog-ng\")", + "query": "event.category:process and event.type:(start or process_started) and\n ((process.name:service and process.args:stop) or\n (process.name:chkconfig and process.args:off) or\n (process.name:systemctl and process.args:(disable or stop or kill)))\n and process.args:(syslog or rsyslog or \"syslog-ng\")\n", "risk_score": 47, "rule_id": "2f8a1226-5720-437d-9c20-e0029deb6194", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json index d695695e2c23b..5f96b81247942 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Application Credential Modification", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Update application - Certificates and secrets management\" and event.outcome:(success or Success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Update application - Certificates and secrets management\" and event.outcome:(success or Success)\n", "references": [ "https://msrc-blog.microsoft.com/2020/12/13/customer-guidance-on-recent-nation-state-cyber-attacks/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json index 31eec6ee4d1c2..d9f70f88a23b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Diagnostic Settings Deletion", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/azure-monitor/platform/diagnostic-settings" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json index 43557bfeb31ba..c715ea42e4b7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Service Principal Addition", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add service principal\" and event.outcome:(success or Success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add service principal\" and event.outcome:(success or Success)\n", "references": [ "https://msrc-blog.microsoft.com/2020/12/13/customer-guidance-on-recent-nation-state-cyber-attacks/", "https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal" 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 bedb41cb726a6..1cc353f1add3a 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Base16 or Base32 Encoding/Decoding Activity", - "query": "event.category:process and event.type:(start or process_started) and process.name:(base16 or base32 or base32plain or base32hex)", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:(base16 or base32 or base32plain or base32hex)\n", "risk_score": 21, "rule_id": "debff20a-46bc-4a4d-bae5-5cdd14222795", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json index ca7485a29f4fc..d04c2b2a38915 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Windows Event Logs Cleared", - "query": "event.action:(\"audit-log-cleared\" or \"Log clear\")", + "query": "event.action:(\"audit-log-cleared\" or \"Log clear\")\n", "risk_score": 21, "rule_id": "45ac4800-840f-414c-b221-53dd36a5aaf7", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json index 8daf17dc8c386..744543ab8a1f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudTrail Log Deleted", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:DeleteTrail and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:DeleteTrail and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_DeleteTrail.html", "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/delete-trail.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json index c140a1f9b9734..27990a74ac5b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudTrail Log Suspended", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:StopLogging and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:StopLogging and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_StopLogging.html", "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/stop-logging.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json index b5984d5d624c3..61806b640fae2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudWatch Alarm Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:monitoring.amazonaws.com and event.action:DeleteAlarms and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:monitoring.amazonaws.com and event.action:DeleteAlarms and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudwatch/delete-alarms.html", "https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_DeleteAlarms.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json index a512cd8c842ee..f58164d1a483f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "AWS Config Service Tampering", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:config.amazonaws.com and event.action:(DeleteConfigRule or DeleteOrganizationConfigRule or DeleteConfigurationAggregator or DeleteConfigurationRecorder or DeleteConformancePack or DeleteOrganizationConformancePack or DeleteDeliveryChannel or DeleteRemediationConfiguration or DeleteRetentionConfiguration)", + "query": "event.dataset:aws.cloudtrail and event.provider:config.amazonaws.com and\n event.action:(DeleteConfigRule or DeleteOrganizationConfigRule or DeleteConfigurationAggregator or\n DeleteConfigurationRecorder or DeleteConformancePack or DeleteOrganizationConformancePack or\n DeleteDeliveryChannel or DeleteRemediationConfiguration or DeleteRetentionConfiguration)\n", "references": [ "https://docs.aws.amazon.com/config/latest/developerguide/how-does-config-work.html", "https://docs.aws.amazon.com/config/latest/APIReference/API_Operations.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json index abadb416deec8..c222b25721292 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS Configuration Recorder Stopped", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:config.amazonaws.com and event.action:StopConfigurationRecorder and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:config.amazonaws.com and event.action:StopConfigurationRecorder and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configservice/stop-configuration-recorder.html", "https://docs.aws.amazon.com/config/latest/APIReference/API_StopConfigurationRecorder.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json index c7ac34a39563e..fba792e80d490 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json @@ -10,7 +10,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Windows CryptoAPI Spoofing Vulnerability (CVE-2020-0601 - CurveBall)", - "query": "event.provider:\"Microsoft-Windows-Audit-CVE\" and message:\"[CVE-2020-0601]\"", + "query": "event.provider:\"Microsoft-Windows-Audit-CVE\" and message:\"[CVE-2020-0601]\"\n", "risk_score": 21, "rule_id": "56557cde-d923-4b88-adee-c61b3f3b5dc3", "severity": "low", 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 0a51b0ccc87f5..cc84001428ff0 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Disabling of SELinux", - "query": "event.category:process and event.type:(start or process_started) and process.name:setenforce and process.args:0", + "query": "event.category:process and event.type:(start or process_started) and process.name:setenforce and process.args:0\n", "risk_score": 47, "rule_id": "eb9eb8ba-a983-41d9-9c93-a1c05112ca5e", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json index f88b8b3589d93..8c74783642395 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS EC2 Flow Log Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:DeleteFlowLogs and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:DeleteFlowLogs and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/delete-flow-logs.html", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteFlowLogs.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json index 06fc3a3f094a5..b64f7eed4be8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS EC2 Network Access Control List Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:(DeleteNetworkAcl or DeleteNetworkAclEntry) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:(DeleteNetworkAcl or DeleteNetworkAclEntry) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/delete-network-acl.html", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteNetworkAcl.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json index 0ea25e05915c6..a8a2f945c76e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Event Hub Deletion", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.EVENTHUB/NAMESPACES/EVENTHUBS/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.EVENTHUB/NAMESPACES/EVENTHUBS/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-about", "https://azure.microsoft.com/en-in/services/event-hubs/", 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 0b934676ac1eb..6d86f27cd52b4 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "File Deletion via Shred", - "query": "event.category:process and event.type:(start or process_started) and process.name:shred and process.args:(\"-u\" or \"--remove\" or \"-z\" or \"--zero\")", + "query": "event.category:process and event.type:(start or process_started) and process.name:shred and\n process.args:(\"-u\" or \"--remove\" or \"-z\" or \"--zero\")\n", "risk_score": 21, "rule_id": "a1329140-8de3-4445-9f87-908fb6d824f4", "severity": "low", 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 cd83733d81830..be7e8b71694ba 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "File Permission Modification in Writable Directory", - "query": "event.category:process and event.type:(start or process_started) and process.name:(chmod or chown or chattr or chgrp) and process.working_directory:(/tmp or /var/tmp or /dev/shm) and not user.name:root", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:(chmod or chown or chattr or chgrp) and\n process.working_directory:(/tmp or /var/tmp or /dev/shm) and\n not user.name:root\n", "risk_score": 21, "rule_id": "9f9a2a82-93a8-4b1a-8778-1780895626d4", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json index f8961a832ac1e..98ee5f6eb8cbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Firewall Policy Deletion", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.NETWORK/FIREWALLPOLICIES/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.NETWORK/FIREWALLPOLICIES/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/firewall-manager/policy-overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json index 7fbe044738386..ff2a20f85d3e3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Firewall Rule Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.insert", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.insert\n", "references": [ "https://cloud.google.com/vpc/docs/firewalls" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json index 4cc8fde077c3e..3fc9b6bc49d3d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Firewall Rule Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.delete", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.delete\n", "references": [ "https://cloud.google.com/vpc/docs/firewalls" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json index 614da7f79a46b..e6ad1fc554183 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Firewall Rule Modification", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.patch", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.firewalls.patch\n", "references": [ "https://cloud.google.com/vpc/docs/firewalls" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json index 805f21f875f02..d2fd746f8971e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Logging Bucket Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.DeleteBucket and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.DeleteBucket and event.outcome:success\n", "references": [ "https://cloud.google.com/logging/docs/buckets", "https://cloud.google.com/logging/docs/storage" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json index 6fa62fe3a6313..3e103413967fe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Logging Sink Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.DeleteSink and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.DeleteSink and event.outcome:success\n", "references": [ "https://cloud.google.com/logging/docs/export" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json index 4907e0f13e550..78435128865f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Pub/Sub Subscription Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Subscriber.DeleteSubscription and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Subscriber.DeleteSubscription and event.outcome:success\n", "references": [ "https://cloud.google.com/pubsub/docs/overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json index 7632a73ddfa90..eb6945e88e3fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Pub/Sub Topic Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Publisher.DeleteTopic and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.pubsub.v*.Publisher.DeleteTopic and event.outcome:success\n", "references": [ "https://cloud.google.com/pubsub/docs/overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json index abb6912f0828d..063e844c990be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Storage Bucket Configuration Modification", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.buckets.update\" and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.buckets.update\" and event.outcome:success\n", "references": [ "https://cloud.google.com/storage/docs/key-terms#buckets" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json index 8eb238c9dc796..c0a3c90853d79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Storage Bucket Permissions Modification", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.setIamPermissions\" and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.setIamPermissions\" and event.outcome:success\n", "references": [ "https://cloud.google.com/storage/docs/access-control/iam-permissions" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json index 8ededad39f415..7132fed195ccf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS GuardDuty Detector Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:guardduty.amazonaws.com and event.action:DeleteDetector and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:guardduty.amazonaws.com and event.action:DeleteDetector and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/guardduty/delete-detector.html", "https://docs.aws.amazon.com/guardduty/latest/APIReference/API_DeleteDetector.html" 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 a14258e533275..fe8014936316c 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 @@ -15,7 +15,7 @@ "license": "Elastic License v2", "max_signals": 33, "name": "Creation of Hidden Files and Directories", - "query": "event.category:process AND event.type:(start or process_started) AND process.working_directory:(\"/tmp\" or \"/var/tmp\" or \"/dev/shm\") AND process.args:/\\.[a-zA-Z0-9_\\-][a-zA-Z0-9_\\-\\.]{1,254}/ AND NOT process.name:(ls or find)", + "query": "event.category:process AND event.type:(start or process_started) AND\n process.working_directory:(\"/tmp\" or \"/var/tmp\" or \"/dev/shm\") AND\n process.args:/\\.[a-zA-Z0-9_\\-][a-zA-Z0-9_\\-\\.]{1,254}/ AND\n NOT process.name:(ls or find)\n", "risk_score": 47, "rule_id": "b9666521-4742-49ce-9ddc-b8e84c35acae", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json index 2ddcd0c640415..43d662df271ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json @@ -13,7 +13,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Process Injection by the Microsoft Build Engine", - "query": "process.name:MSBuild.exe and event.action:\"CreateRemoteThread detected (rule: CreateRemoteThread)\"", + "query": "process.name:MSBuild.exe and event.action:\"CreateRemoteThread detected (rule: CreateRemoteThread)\"\n", "risk_score": 21, "rule_id": "9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae9", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json index 6585c1b8c8ffb..3397db22f51f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Install Root Certificate", - "query": "event.category:process and event.type:(start or process_started) and process.name:security and process.args:\"add-trusted-cert\"", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:security and process.args:\"add-trusted-cert\"\n", "references": [ "https://ss64.com/osx/security-cert.html" ], 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 691c30d6f42a5..35c0cb2fb2645 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Kernel Module Removal", - "query": "event.category:process and event.type:(start or process_started) and process.args:((rmmod and sudo) or (modprobe and sudo and (\"--remove\" or \"-r\")))", + "query": "event.category:process and event.type:(start or process_started) and\n process.args:((rmmod and sudo) or (modprobe and sudo and (\"--remove\" or \"-r\")))\n", "references": [ "http://man7.org/linux/man-pages/man8/modprobe.8.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json index b39aee071a874..f4c3e3476c0b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange DLP Policy Removed", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-DlpPolicy\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-DlpPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-dlppolicy?view=exchange-ps", "https://docs.microsoft.com/en-us/microsoft-365/compliance/data-loss-prevention-policies?view=o365-worldwide" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json index c4de503b66540..ab3399bddbe7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Malware Filter Policy Deletion", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-MalwareFilterPolicy\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-MalwareFilterPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-malwarefilterpolicy?view=exchange-ps" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json index c0dcec7e1b0d7..06728dee5b150 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Malware Filter Rule Modification", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-MalwareFilterRule\" or \"Disable-MalwareFilterRule\") and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-MalwareFilterRule\" or \"Disable-MalwareFilterRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-malwarefilterrule?view=exchange-ps", "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-malwarefilterrule?view=exchange-ps" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json index 5273115ead302..50af384100139 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Safe Attachment Rule Disabled", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeAttachmentRule\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeAttachmentRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-safeattachmentrule?view=exchange-ps" ], 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 b61a45e1287b7..5f45aa836ddf5 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 @@ -12,7 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Signed Binary", - "query": "sequence by process.entity_id\n [process where (process.name : \"expand.exe\" or process.name : \"extrac.exe\" or\n process.name : \"ieexec.exe\" or process.name : \"makecab.exe\") and\n event.type == \"start\"]\n [network where (process.name : \"expand.exe\" or process.name : \"extrac.exe\" or\n process.name : \"ieexec.exe\" or process.name : \"makecab.exe\") and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")]\n", + "query": "sequence by process.entity_id\n [process where (process.name : \"expand.exe\" or process.name : \"extrac.exe\" or\n process.name : \"ieexec.exe\" or process.name : \"makecab.exe\") and\n event.type == \"start\"]\n [network where (process.name : \"expand.exe\" or process.name : \"extrac.exe\" or\n process.name : \"ieexec.exe\" or process.name : \"makecab.exe\") and\n not cidrmatch(destination.ip,\n \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\", \"192.0.0.0/29\", \"192.0.0.8/32\",\n \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\",\n \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\", \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "63e65ec3-43b1-45b0-8f2d-45b34291dc44", "severity": "low", @@ -50,5 +53,5 @@ } ], "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json index d41804247945b..aa0efa290c4f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Modification of Environment Variable via Launchctl", - "query": "event.category:process and event.type:start and process.name:launchctl and process.args:(setenv and not (JAVA*_HOME or RUNTIME_JAVA_HOME or DBUS_LAUNCHD_SESSION_BUS_SOCKET or ANT_HOME or LG_WEBOS_TV_SDK_HOME or WEBOS_CLI_TV or EDEN_ENV) ) and not process.parent.executable:(\"/Applications/NoMachine.app/Contents/Frameworks/bin/nxserver.bin\" or \"/usr/local/bin/kr\" or \"/Applications/NoMachine.app/Contents/Frameworks/bin/nxserver.bin\" or \"/Applications/IntelliJ IDEA CE.app/Contents/jbr/Contents/Home/lib/jspawnhelper\")", + "query": "event.category:process and event.type:start and\n process.name:launchctl and\n process.args:(setenv and not (JAVA*_HOME or\n RUNTIME_JAVA_HOME or\n DBUS_LAUNCHD_SESSION_BUS_SOCKET or\n ANT_HOME or\n LG_WEBOS_TV_SDK_HOME or\n WEBOS_CLI_TV or\n EDEN_ENV)\n ) and\n not process.parent.executable:(\"/Applications/NoMachine.app/Contents/Frameworks/bin/nxserver.bin\" or\n \"/usr/local/bin/kr\" or\n \"/Applications/NoMachine.app/Contents/Frameworks/bin/nxserver.bin\" or\n \"/Applications/IntelliJ IDEA CE.app/Contents/jbr/Contents/Home/lib/jspawnhelper\")\n", "references": [ "https://github.com/rapid7/metasploit-framework/blob/master//modules/post/osx/escalate/tccbypass.rb" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json index 6d6541269030d..8f8871b75022e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json @@ -12,7 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via MsXsl", - "query": "sequence by process.entity_id\n [process where process.name : \"msxsl.exe\" and event.type == \"start\"]\n [network where process.name : \"msxsl.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")]\n", + "query": "sequence by process.entity_id\n [process where process.name : \"msxsl.exe\" and event.type == \"start\"]\n [network where process.name : \"msxsl.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "b86afe07-0d98-4738-b15d-8d7465f95ff5", "severity": "low", @@ -41,5 +44,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json index 27a590f49cb31..030e72dd4a411 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Network Watcher Deletion", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.NETWORK/NETWORKWATCHERS/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.NETWORK/NETWORKWATCHERS/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/network-watcher/network-watcher-monitoring-overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json index 4eb3bb47beff5..d673b7ef324f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS S3 Bucket Configuration Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:s3.amazonaws.com and event.action:(DeleteBucketPolicy or DeleteBucketReplication or DeleteBucketCors or DeleteBucketEncryption or DeleteBucketLifecycle) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:s3.amazonaws.com and\n event.action:(DeleteBucketPolicy or DeleteBucketReplication or DeleteBucketCors or\n DeleteBucketEncryption or DeleteBucketLifecycle)\n and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketPolicy.html", "https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json index 8021a21c8010d..a56b2cf6e6ded 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Modification of Safari Settings via Defaults Command", - "query": "event.category:process and event.type:start and process.name:defaults and process.args: (com.apple.Safari and write and not ( UniversalSearchEnabled or SuppressSearchSuggestions or WebKitTabToLinksPreferenceKey or ShowFullURLInSmartSearchField or com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks ) )", + "query": "event.category:process and event.type:start and\n process.name:defaults and process.args:\n (com.apple.Safari and write and not\n (\n UniversalSearchEnabled or\n SuppressSearchSuggestions or\n WebKitTabToLinksPreferenceKey or\n ShowFullURLInSmartSearchField or\n com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks\n )\n )\n", "references": [ "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json index 338010e1fe0e3..385f89910ab7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Microsoft Office Sandbox Evasion", - "query": "event.category:file and not event.type:deletion and file.name:~$*.zip", + "query": "event.category:file and not event.type:deletion and file.name:~$*.zip\n", "references": [ "https://i.blackhat.com/USA-20/Wednesday/us-20-Wardle-Office-Drama-On-macOS.pdf", "https://www.mdsec.co.uk/2018/08/escaping-the-sandbox-microsoft-office-on-macos/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json index 8339c75c59c52..86903058b62fe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "High Number of Process and/or Service Terminations", - "query": "event.category:process and event.type:start and process.name:(net.exe or sc.exe or taskkill.exe) and process.args:(stop or pause or delete or \"/PID\" or \"/IM\" or \"/T\" or \"/F\" or \"/t\" or \"/f\" or \"/im\" or \"/pid\")", + "query": "event.category:process and event.type:start and process.name:(net.exe or sc.exe or taskkill.exe) and\n process.args:(stop or pause or delete or \"/PID\" or \"/IM\" or \"/T\" or \"/F\" or \"/t\" or \"/f\" or \"/im\" or \"/pid\")\n", "risk_score": 47, "rule_id": "035889c4-2686-4583-a7df-67f89c292f2c", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json new file mode 100644 index 0000000000000..b05402f419f5a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json @@ -0,0 +1,89 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies when a script interpreter or signed binary is launched via a non-standard working directory. An attacker may use this technique to evade defenses.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Suspicious Execution from a Mounted Device", + "query": "process where event.type == \"start\" and process.executable : \"C:\\\\*\" and\n (process.working_directory : \"?:\\\\\" and not process.working_directory: \"C:\\\\\") and\n process.parent.name : \"explorer.exe\" and\n process.name : (\"rundll32.exe\", \"mshta.exe\", \"powershell.exe\", \"pwsh.exe\", \"cmd.exe\", \"regsvr32.exe\",\n \"cscript.exe\", \"wscript.exe\")\n", + "references": [ + "https://www.microsoft.com/security/blog/2021/05/27/new-sophisticated-email-based-attack-from-nobelium/", + "https://www.volexity.com/blog/2021/05/27/suspected-apt29-operation-launches-election-fraud-themed-phishing-campaigns/" + ], + "risk_score": 47, + "rule_id": "8a1d4831-3ce6-4859-9891-28931fa6101d", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1218", + "name": "Signed Binary Proxy Execution", + "reference": "https://attack.mitre.org/techniques/T1218/", + "subtechnique": [ + { + "id": "T1218.011", + "name": "Rundll32", + "reference": "https://attack.mitre.org/techniques/T1218/011/" + }, + { + "id": "T1218.005", + "name": "Mshta", + "reference": "https://attack.mitre.org/techniques/T1218/005/" + }, + { + "id": "T1218.010", + "name": "Regsvr32", + "reference": "https://attack.mitre.org/techniques/T1218/010/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.001", + "name": "PowerShell", + "reference": "https://attack.mitre.org/techniques/T1059/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json index b1ab19a25d840..d33620cbbf63b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json @@ -1,8 +1,10 @@ { "author": [ - "Elastic" + "Elastic", + "@BenB196", + "Austin Songer" ], - "description": "Identifies a high number of Okta user password reset or account unlock attempts. An adversary may attempt to obtain unauthorized access to an Okta user account using these methods and attempt to blend in with normal activity in their target's environment and evade detection.", + "description": "Identifies a high number of Okta user password reset or account unlock attempts. An adversary may attempt to obtain unauthorized access to Okta user accounts using these methods and attempt to blend in with normal activity in their target's environment and evade detection.", "false_positives": [ "The number of Okta user password reset or account unlock attempts will likely vary between organizations. To fit this rule to their organization, users can duplicate this rule and edit the schedule and threshold values in the new rule." ], @@ -15,7 +17,7 @@ "license": "Elastic License v2", "name": "High Number of Okta User Password Reset or Unlock Attempts", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:(system.email.account_unlock.sent_message or system.email.password_reset.sent_message or system.sms.send_account_unlock_message or system.sms.send_password_reset_message or system.voice.send_account_unlock_call or system.voice.send_password_reset_call or user.account.unlock_token)", + "query": "event.dataset:okta.system and\n event.action:(system.email.account_unlock.sent_message or system.email.password_reset.sent_message or\n system.sms.send_account_unlock_message or system.sms.send_password_reset_message or\n system.voice.send_account_unlock_call or system.voice.send_password_reset_call or\n user.account.unlock_token)\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" @@ -80,10 +82,10 @@ ], "threshold": { "field": [ - "okta.actor.id" + "okta.actor.alternate_id" ], "value": 5 }, "type": "threshold", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json index 963eb444305c7..4b1f13a10414d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "TCC Bypass via Mounted APFS Snapshot Access", - "query": "event.category : process and event.type : (start or process_started) and process.name : mount_apfs and process.args : (/System/Volumes/Data and noowners)", + "query": "event.category : process and event.type : (start or process_started) and process.name : mount_apfs and\n process.args : (/System/Volumes/Data and noowners)\n", "references": [ "https://theevilbit.github.io/posts/cve_2020_9771/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json index dc854dee776c4..aadcaadaba980 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Unload Elastic Endpoint Security Kernel Extension", - "query": "event.category:process and event.type:(start or process_started) and process.name:kextunload and process.args:(\"/System/Library/Extensions/EndpointSecurity.kext\" or \"EndpointSecurity.kext\")", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:kextunload and process.args:(\"/System/Library/Extensions/EndpointSecurity.kext\" or \"EndpointSecurity.kext\")\n", "risk_score": 73, "rule_id": "70fa1af4-27fd-4f26-bd03-50b6af6b9e24", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json new file mode 100644 index 0000000000000..a030c88941a41 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies unusual instances of dllhost.exe making outbound network connections. This may indicate adversarial Command and Control activity.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Unusual Network Connection via DllHost", + "query": "sequence by host.id, process.entity_id with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and process.name : \"dllhost.exe\" and process.args_count == 1]\n [network where process.name : \"dllhost.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\", \"192.0.2.0/24\",\n \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\",\n \"192.175.48.0/24\", \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\", \"FE80::/10\",\n \"FF00::/8\")]\n", + "references": [ + "https://www.microsoft.com/security/blog/2021/05/27/new-sophisticated-email-based-attack-from-nobelium/", + "https://www.volexity.com/blog/2021/05/27/suspected-apt29-operation-launches-election-fraud-themed-phishing-campaigns/", + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], + "risk_score": 47, + "rule_id": "c7894234-7814-44c2-92a9-f7d851ea246a", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1218", + "name": "Signed Binary Proxy Execution", + "reference": "https://attack.mitre.org/techniques/T1218/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json index d920c4f853dfd..f37b6a21c7e58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json @@ -12,7 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Unusual Network Connection via RunDLL32", - "query": "sequence by host.id, process.entity_id with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and process.name : \"rundll32.exe\" and process.args_count == 1]\n [network where process.name : \"rundll32.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\", \"127.0.0.0/8\", \"FE80::/10\", \"::1/128\")]\n", + "query": "sequence by host.id, process.entity_id with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and process.name : \"rundll32.exe\" and process.args_count == 1]\n [network where process.name : \"rundll32.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 47, "rule_id": "52aaab7b-b51c-441a-89ce-4387b3aea886", "severity": "medium", @@ -48,5 +51,5 @@ } ], "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json index cc2d47812b48c..9a7c7c9f668cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS WAF Access Control List Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.action:DeleteWebACL and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.action:DeleteWebACL and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/waf-regional/delete-web-acl.html", "https://docs.aws.amazon.com/waf/latest/APIReference/API_wafRegional_DeleteWebACL.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json index ee21d03f8464b..6f42caa63cf49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS WAF Rule or Rule Group Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.action:(DeleteRule or DeleteRuleGroup) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.action:(DeleteRule or DeleteRuleGroup) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/waf/delete-rule-group.html", "https://docs.aws.amazon.com/waf/latest/APIReference/API_waf_DeleteRuleGroup.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json index 16eabb82de0f7..ed50b58a91e13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Blob Container Access Level Modification", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.STORAGE/STORAGEACCOUNTS/BLOBSERVICES/CONTAINERS/WRITE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.STORAGE/STORAGEACCOUNTS/BLOBSERVICES/CONTAINERS/WRITE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/storage/blobs/anonymous-read-access-prevent" ], 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 01d07b03c079a..96860eb7e6f79 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Enumeration of Kernel Modules", - "query": "event.category:process and event.type:(start or process_started) and process.args:(kmod and list and sudo or sudo and (depmod or lsmod or modinfo))", + "query": "event.category:process and event.type:(start or process_started) and\n process.args:(kmod and list and sudo or sudo and (depmod or lsmod or modinfo))\n", "risk_score": 47, "rule_id": "2d8043ed-5bda-4caf-801c-c1feb7410504", "severity": "medium", 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 7defca7b649de..09adcdc65b02a 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Virtual Machine Fingerprinting", - "query": "event.category:process and event.type:(start or process_started) and process.args:(\"/sys/class/dmi/id/bios_version\" or \"/sys/class/dmi/id/product_name\" or \"/sys/class/dmi/id/chassis_vendor\" or \"/proc/scsi/scsi\" or \"/proc/ide/hd0/model\") and not user.name:root", + "query": "event.category:process and event.type:(start or process_started) and\n process.args:(\"/sys/class/dmi/id/bios_version\" or\n \"/sys/class/dmi/id/product_name\" or\n \"/sys/class/dmi/id/chassis_vendor\" or\n \"/proc/scsi/scsi\" or\n \"/proc/ide/hd0/model\") and\n not user.name:root\n", "risk_score": 73, "rule_id": "5b03c9fb-9945-4d2f-9568-fd690fee3fba", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json index 96f246f9403bf..300840771081d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Domain Added to Google Workspace Trusted Domains", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ADD_TRUSTED_DOMAINS", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ADD_TRUSTED_DOMAINS\n", "references": [ "https://support.google.com/a/answer/6160020?hl=en" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json index c3fb6d83f194f..63bf6fea698ae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json @@ -20,7 +20,7 @@ "license": "Elastic License v2", "max_signals": 10000, "name": "Endpoint Security", - "query": "event.kind:alert and event.module:(endpoint and not endgame)", + "query": "event.kind:alert and event.module:(endpoint and not endgame)\n", "risk_score": 47, "risk_score_mapping": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json index bf53625cef750..ff690710b5ba3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Adversary Behavior - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and (event.action:rules_engine_event or endgame.event_subtype_full:rules_engine_event)", + "query": "event.kind:alert and event.module:endgame and (event.action:rules_engine_event or endgame.event_subtype_full:rules_engine_event)\n", "risk_score": 47, "rule_id": "77a3c3df-8ec4-4da4-b758-878f551dee69", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_detected.json index 43cb19f50d675..4aaf9938c29da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Credential Dumping - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)\n", "risk_score": 73, "rule_id": "571afc56-5ed9-465d-a2a9-045f099f6e7e", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_prevented.json index 29b5bc3f39cf1..11e8dece47fb5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_dumping_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Credential Dumping - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)\n", "risk_score": 47, "rule_id": "db8c33a8-03cd-4988-9e2c-d0a4863adb13", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_detected.json index 393591a241114..b6753cbbf784e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Credential Manipulation - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)\n", "risk_score": 73, "rule_id": "c0be5f31-e180-48ed-aa08-96b36899d48f", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_prevented.json index e9ca199c4a791..9f409b00d6421 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_cred_manipulation_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Credential Manipulation - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)\n", "risk_score": 47, "rule_id": "c9e38e64-3f4c-4bf3-ad48-0e61a60ea1fa", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_detected.json index a169582c2da92..0cc778cc12714 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Exploit - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)\n", "risk_score": 73, "rule_id": "2003cdc8-8d83-4aa5-b132-1f9a8eb48514", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_prevented.json index b781a1fae1847..fe7218fe878ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_exploit_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Exploit - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)\n", "risk_score": 47, "rule_id": "2863ffeb-bf77-44dd-b7a5-93ef94b72036", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json index f7a064961f039..92795c3811345 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Malware - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)\n", "risk_score": 99, "rule_id": "0a97b20f-4144-49ea-be32-b540ecc445de", "severity": "critical", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json index 59cbd98e2d42b..f712f3f1b221d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Malware - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)\n", "risk_score": 73, "rule_id": "3b382770-efbb-44f4-beed-f5e0a051b895", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_detected.json index b3db96d6d121b..96b64c026ad0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Permission Theft - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)\n", "risk_score": 73, "rule_id": "c3167e1b-f73c-41be-b60b-87f4df707fe3", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_prevented.json index 18b316a293da8..fcb453d16100f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_permission_theft_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Permission Theft - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)\n", "risk_score": 47, "rule_id": "453f659e-0429-40b1-bfdb-b6957286e04b", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_detected.json index 861daa2d004c7..83a8ff2f83c8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Process Injection - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)\n", "risk_score": 73, "rule_id": "80c52164-c82a-402c-9964-852533d58be1", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_prevented.json index 5f78a3517e931..e171fbcf6bb83 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_process_injection_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Process Injection - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)\n", "risk_score": 47, "rule_id": "990838aa-a953-4f3e-b3cb-6ddf7584de9e", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json index 4c060bb52f32f..b12352ee144d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Ransomware - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)\n", "risk_score": 99, "rule_id": "8cb4f625-7743-4dfb-ae1b-ad92be9df7bd", "severity": "critical", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json index 78845ffc4c845..22150cd72a4c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Ransomware - Prevented - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)\n", "risk_score": 73, "rule_id": "e3c5d5cb-41d5-4206-805c-f30561eae3ac", "severity": "high", 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 32673c7800f0b..e5bef64a4c747 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 @@ -15,7 +15,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Command Prompt Network Connection", - "query": "sequence by process.entity_id\n [process where process.name : \"cmd.exe\" and event.type == \"start\"]\n [network where process.name : \"cmd.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")]\n", + "query": "sequence by process.entity_id\n [process where process.name : \"cmd.exe\" and event.type == \"start\"]\n [network where process.name : \"cmd.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "89f9a4b0-9f8f-4ee0-8823-c4751a6d6696", "severity": "low", @@ -59,5 +62,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json index 8254e9e1c75e0..1292596404eff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Command Execution on Virtual Machine", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION\" and event.outcome:(Success or success)\n", "references": [ "https://adsecurity.org/?p=4277", "https://posts.specterops.io/attacking-azure-azure-ad-and-introducing-powerzure-ca70b330511a", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json index cb3c3b3d040f3..4bf6c272e59ae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Execution via Electron Child Process Node.js Module", - "query": "event.category:process and event.type:(start or process_started) and process.args:(\"-e\" and const*require*child_process*)", + "query": "event.category:process and event.type:(start or process_started) and process.args:(\"-e\" and const*require*child_process*)\n", "references": [ "https://www.matthewslipper.com/2019/09/22/everything-you-wanted-electron-child-process.html", "https://www.trustedsec.com/blog/macos-injection-via-third-party-frameworks/", 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 31a81001d9e04..abc41d9f6d5c3 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 @@ -12,7 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Compiled HTML File", - "query": "sequence by process.entity_id\n [process where process.name : \"hh.exe\" and event.type == \"start\"]\n [network where process.name : \"hh.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\")]\n", + "query": "sequence by process.entity_id\n [process where process.name : \"hh.exe\" and event.type == \"start\"]\n [network where process.name : \"hh.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "b29ee2be-bf99-446c-ab1a-2dc0183394b8", "severity": "low", @@ -57,5 +60,5 @@ } ], "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_spawned_network_event.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_spawned_network_event.json index 6fce74fb44b12..5781c25789b94 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_spawned_network_event.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_spawned_network_event.json @@ -13,9 +13,10 @@ "language": "eql", "license": "Elastic License v2", "name": "macOS Installer Spawns Network Event", - "query": "sequence by process.entity_id with maxspan=1m\n [ process where event.type == \"start\" and host.os.family == \"macos\" and \n process.parent.executable in (\"/usr/sbin/installer\", \"/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer\") ]\n [ network where not cidrmatch(destination.ip,\n \"192.168.0.0/16\",\n \"10.0.0.0/8\",\n \"172.16.0.0/12\",\n \"224.0.0.0/8\",\n \"127.0.0.0/8\",\n \"169.254.0.0/16\",\n \"::1\",\n \"FE80::/10\",\n \"FF00::/8\") ]\n", + "query": "sequence by process.entity_id with maxspan=1m\n [process where event.type == \"start\" and host.os.family == \"macos\" and\n process.parent.executable in (\"/usr/sbin/installer\", \"/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer\") ]\n [network where not cidrmatch(destination.ip,\n \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\", \"192.0.0.0/29\", \"192.0.0.8/32\",\n \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\",\n \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\", \"FE80::/10\", \"FF00::/8\")]\n", "references": [ - "https://redcanary.com/blog/clipping-silver-sparrows-wings" + "https://redcanary.com/blog/clipping-silver-sparrows-wings", + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" ], "risk_score": 47, "rule_id": "99239e7d-b0d4-46e3-8609-acafcf99f68c", @@ -75,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json index eb453205441a7..77537be3f1cbb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "EggShell Backdoor Execution", - "query": "event.category:process and event.type:(start or process_started) and process.name:espl and process.args:eyJkZWJ1ZyI6*", + "query": "event.category:process and event.type:(start or process_started) and process.name:espl and process.args:eyJkZWJ1ZyI6*\n", "references": [ "https://github.com/neoneggplant/EggShell" ], 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 b2d71b355e3a1..0f1e857232e40 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Interactive Terminal Spawned via Perl", - "query": "event.category:process and event.type:(start or process_started) and process.name:perl and process.args:(\"exec \\\"/bin/sh\\\";\" or \"exec \\\"/bin/dash\\\";\" or \"exec \\\"/bin/bash\\\";\")", + "query": "event.category:process and event.type:(start or process_started) and process.name:perl and\n process.args:(\"exec \\\"/bin/sh\\\";\" or \"exec \\\"/bin/dash\\\";\" or \"exec \\\"/bin/bash\\\";\")\n", "risk_score": 73, "rule_id": "05e5a668-7b51-4a67-93ab-e9af405c9ef3", "severity": "high", 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 402f5b0ab33ff..2c40ff28bfa11 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Interactive Terminal Spawned via Python", - "query": "event.category:process and event.type:(start or process_started) and process.name:python and process.args:(\"import pty; pty.spawn(\\\"/bin/sh\\\")\" or \"import pty; pty.spawn(\\\"/bin/dash\\\")\" or \"import pty; pty.spawn(\\\"/bin/bash\\\")\")", + "query": "event.category:process and event.type:(start or process_started) and process.name:python and\n process.args:(\"import pty; pty.spawn(\\\"/bin/sh\\\")\" or\n \"import pty; pty.spawn(\\\"/bin/dash\\\")\" or\n \"import pty; pty.spawn(\\\"/bin/bash\\\")\")\n", "risk_score": 73, "rule_id": "d76b02ef-fc95-4001-9297-01cb7412232f", "severity": "high", 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 ca919d06e34a4..2ccc730c3fa01 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 @@ -15,7 +15,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Registration Utility", - "query": "sequence by process.entity_id\n [process where event.type == \"start\" and\n process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not (\n user.id == \"S-1-5-18\" and\n (process.parent.name : \"msiexec.exe\" or process.parent.executable : (\"C:\\\\Program Files (x86)\\\\*.exe\", \"C:\\\\Program Files\\\\*.exe\"))\n )\n ]\n [network where process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"169.254.169.254\", \"172.16.0.0/12\", \"192.168.0.0/16\") and network.protocol != \"dns\"]\n", + "query": "sequence by process.entity_id\n [process where event.type == \"start\" and\n process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not (\n user.id == \"S-1-5-18\" and\n (process.parent.name : \"msiexec.exe\" or process.parent.executable : (\"C:\\\\Program Files (x86)\\\\*.exe\", \"C:\\\\Program Files\\\\*.exe\"))\n )\n ]\n [network where process.name : (\"regsvr32.exe\", \"RegAsm.exe\", \"RegSvcs.exe\") and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\",\n \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\",\n \"100.64.0.0/10\", \"192.175.48.0/24\",\"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\") and network.protocol != \"dns\"]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 21, "rule_id": "fb02b8d3-71ee-4af1-bacd-215d23f17efa", "severity": "low", @@ -60,5 +63,5 @@ } ], "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json index 1d6fa8507ac43..74a70eb7ebd93 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json @@ -11,9 +11,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Apple Script Execution followed by Network Connection", - "query": "sequence by host.id, process.entity_id with maxspan=30s\n [process where event.type == \"start\" and process.name == \"osascript\"]\n [network where event.type != \"end\" and process.name == \"osascript\" and destination.ip != \"::1\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \n \"172.16.0.0/12\", \n \"192.168.0.0/16\", \n \"127.0.0.0/8\", \n \"169.254.0.0/16\", \n \"224.0.0.0/4\", \n \"FE80::/10\", \n \"FF00::/8\")\n ]\n", + "query": "sequence by host.id, process.entity_id with maxspan=30s\n [process where event.type == \"start\" and process.name == \"osascript\"]\n [network where event.type != \"end\" and process.name == \"osascript\" and destination.ip != \"::1\" and\n not cidrmatch(destination.ip,\n \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\", \"192.0.0.0/29\", \"192.0.0.8/32\",\n \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\", \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\",\n \"192.52.193.0/24\", \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\", \"FE80::/10\", \"FF00::/8\")]\n", "references": [ - "https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/index.html" + "https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/index.html", + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" ], "risk_score": 47, "rule_id": "47f76567-d58a-4fed-b32b-21f571e28910", @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json index 7c67a3f037538..f4d0877ca70b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS EC2 Snapshot Activity", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:ModifySnapshotAttribute", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:ModifySnapshotAttribute\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/modify-snapshot-attribute.html", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySnapshotAttribute.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json new file mode 100644 index 0000000000000..2bf25435b84de --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json @@ -0,0 +1,70 @@ +{ + "author": [ + "Elastic", + "Austin Songer" + ], + "description": "Identifies an attempt to export an AWS EC2 instance. A virtual machine (VM) export may indicate an attempt to extract or exfiltrate information.", + "false_positives": [ + "VM exports may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. VM exports from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "from": "now-60m", + "index": [ + "filebeat-*", + "logs-aws*" + ], + "interval": "10m", + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS EC2 VM Export Failure", + "note": "## Config\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:CreateInstanceExportTask and event.outcome:failure\n", + "references": [ + "https://docs.aws.amazon.com/vm-import/latest/userguide/vmexport.html#export-instance" + ], + "risk_score": 21, + "rule_id": "e919611d-6b6f-493b-8314-7ed6ac2e413b", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Asset Visibility" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0010", + "name": "Exfiltration", + "reference": "https://attack.mitre.org/tactics/TA0010/" + }, + "technique": [ + { + "id": "T1537", + "name": "Transfer Data to Cloud Account", + "reference": "https://attack.mitre.org/techniques/T1537/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0009", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "technique": [ + { + "id": "T1005", + "name": "Data from Local System", + "reference": "https://attack.mitre.org/techniques/T1005/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json index ddff312a362ee..ac016370fe1c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Logging Sink Modification", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.UpdateSink and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.logging.v*.ConfigServiceV*.UpdateSink and event.outcome:success\n", "references": [ "https://cloud.google.com/logging/docs/export#how_sinks_work" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json index ec9373352254b..a6a9e24ccb63a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Transport Rule Creation", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-TransportRule\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-TransportRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/new-transportrule?view=exchange-ps", "https://docs.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json index 2503f679464f1..836ade6c0b80f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Transport Rule Modification", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-TransportRule\" or \"Disable-TransportRule\") and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-TransportRule\" or \"Disable-TransportRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-transportrule?view=exchange-ps", "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-transportrule?view=exchange-ps", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json index b97e868b21f55..fbcc6e757c66b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "max_signals": 10000, "name": "External Alerts", - "query": "event.kind:alert and not event.module:(endgame or endpoint)", + "query": "event.kind:alert and not event.module:(endgame or endpoint)\n", "risk_score": 47, "risk_score_mapping": [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json index ba84d38d7e4ee..e0a333d92c5aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace Admin Role Deletion", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:DELETE_ROLE", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:DELETE_ROLE\n", "references": [ "https://support.google.com/a/answer/2406043?hl=en" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json index 551133ed2a58d..b05edc0566614 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace MFA Enforcement Disabled", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ENFORCE_STRONG_AUTHENTICATION and gsuite.admin.new_value:false", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ENFORCE_STRONG_AUTHENTICATION and gsuite.admin.new_value:false\n", "references": [ "https://support.google.com/a/answer/9176657?hl=en#" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json index 5893782912b35..58a409570f0db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace Password Policy Modified", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:(CHANGE_APPLICATION_SETTING or CREATE_APPLICATION_SETTING) and gsuite.admin.setting.name:( \"Password Management - Enforce strong password\" or \"Password Management - Password reset frequency\" or \"Password Management - Enable password reuse\" or \"Password Management - Enforce password policy at next login\" or \"Password Management - Minimum password length\" or \"Password Management - Maximum password length\" )", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and\n event.provider:admin and event.category:iam and\n event.action:(CHANGE_APPLICATION_SETTING or CREATE_APPLICATION_SETTING) and\n gsuite.admin.setting.name:(\n \"Password Management - Enforce strong password\" or\n \"Password Management - Password reset frequency\" or\n \"Password Management - Enable password reuse\" or\n \"Password Management - Enforce password policy at next login\" or\n \"Password Management - Minimum password length\" or\n \"Password Management - Maximum password length\"\n )\n", "risk_score": 47, "rule_id": "a99f82f5-8e77-4f8b-b3ce-10c0f6afbc73", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json index aebda3bb268d1..ba55b3fc7a9bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Revoke Okta API Token", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:system.api_token.revoke", + "query": "event.dataset:okta.system and event.action:system.api_token.revoke\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_automation_runbook_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_automation_runbook_deleted.json index fb468ff8f6d7d..83fd544c51f13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_automation_runbook_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_automation_runbook_deleted.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Automation Runbook Deleted", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://powerzure.readthedocs.io/en/latest/Functions/operational.html#create-backdoor", "https://github.com/hausec/PowerZure", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json index 9d1c0d3758a9d..196f54bfc174d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudTrail Log Updated", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:UpdateTrail and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:UpdateTrail and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/awscloudtrail/latest/APIReference/API_UpdateTrail.html", "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/update-trail.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json index 14f0419cb4073..f92d6ec44d655 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudWatch Log Group Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:logs.amazonaws.com and event.action:DeleteLogGroup and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:logs.amazonaws.com and event.action:DeleteLogGroup and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/delete-log-group.html", "https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DeleteLogGroup.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json index 6d37a106aa9ab..46ac2d19889f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS CloudWatch Log Stream Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:logs.amazonaws.com and event.action:DeleteLogStream and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:logs.amazonaws.com and event.action:DeleteLogStream and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/delete-log-stream.html", "https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DeleteLogStream.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json index 94b1839dfc5dd..04f1adaee3edb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS EC2 Encryption Disabled", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:DisableEbsEncryptionByDefault and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:DisableEbsEncryptionByDefault and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html", "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/disable-ebs-encryption-by-default.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json index 5ed1aa4386de4..76901da74ce96 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP IAM Role Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteRole and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteRole and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/understanding-roles" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json index 134278f30abb6..e928a60c132f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Service Account Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteServiceAccount and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteServiceAccount and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/service-accounts" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json index a7750fc586729..6c205987eaf17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Service Account Disabled", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DisableServiceAccount and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DisableServiceAccount and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/service-accounts" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json index c534dae963970..c3aa1523e4d1e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Storage Bucket Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.buckets.delete\"", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:\"storage.buckets.delete\"\n", "references": [ "https://cloud.google.com/storage/docs/key-terms#buckets" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_network_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_network_deleted.json index caad64c558863..2748a1f573e9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_network_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_network_deleted.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Virtual Private Cloud Network Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.networks.delete and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.networks.delete and event.outcome:success\n", "references": [ "https://cloud.google.com/vpc/docs/vpc" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json index 7e2090de29c62..ef4ee82058d32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_created.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Virtual Private Cloud Route Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:(v*.compute.routes.insert or \"beta.compute.routes.insert\")", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:(v*.compute.routes.insert or \"beta.compute.routes.insert\")\n", "references": [ "https://cloud.google.com/vpc/docs/routes", "https://cloud.google.com/vpc/docs/using-routes" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_deleted.json index a8b8b12b60960..0d4f5591c9688 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_deleted.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_virtual_private_cloud_route_deleted.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Virtual Private Cloud Route Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.routes.delete and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:v*.compute.routes.delete and event.outcome:success\n", "references": [ "https://cloud.google.com/vpc/docs/routes", "https://cloud.google.com/vpc/docs/using-routes" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json index 7b77a414dd1e0..cb4f1a15cd337 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "AWS IAM Deactivation of MFA Device", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:(DeactivateMFADevice or DeleteVirtualMFADevice) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:(DeactivateMFADevice or DeleteVirtualMFADevice) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/deactivate-mfa-device.html", "https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeactivateMFADevice.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json index 2c43b0560e660..e1f5fcbf4836e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS IAM Group Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:DeleteGroup and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:DeleteGroup and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/delete-group.html", "https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteGroup.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json index 649c859de8184..7372eb0e0286b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Possible Okta DoS Attack", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:(application.integration.rate_limit_exceeded or system.org.rate_limit.warning or system.org.rate_limit.violation or core.concurrency.org.limit.violation)", + "query": "event.dataset:okta.system and event.action:(application.integration.rate_limit_exceeded or system.org.rate_limit.warning or system.org.rate_limit.violation or core.concurrency.org.limit.violation)\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json index 94837f665af75..08ae6ce11bbb5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_cluster_deletion.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS RDS Cluster Deletion", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(DeleteDBCluster or DeleteGlobalCluster) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(DeleteDBCluster or DeleteGlobalCluster) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/delete-db-cluster.html", "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteDBCluster.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json index 065c39f8f676b..e35a0c145ac59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS RDS Instance/Cluster Stoppage", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(StopDBCluster or StopDBInstance) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(StopDBCluster or StopDBInstance) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/stop-db-cluster.html", "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_StopDBCluster.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json index 221f1b1e46eb2..16153a832dcb9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Resource Group Deletion", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.RESOURCES/SUBSCRIPTIONS/RESOURCEGROUPS/DELETE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.RESOURCES/SUBSCRIPTIONS/RESOURCEGROUPS/DELETE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index e910b1a10f586..dc3ca4ceed4c8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -544,7 +544,11 @@ import rule531 from './ml_spike_in_traffic_to_a_country.json'; import rule532 from './command_and_control_tunneling_via_earthworm.json'; import rule533 from './lateral_movement_evasion_rdp_shadowing.json'; import rule534 from './threat_intel_module_match.json'; -import rule535 from './persistence_via_bits_job_notify_command.json'; +import rule535 from './exfiltration_ec2_vm_export_failure.json'; +import rule536 from './defense_evasion_suspicious_execution_from_mounted_device.json'; +import rule537 from './defense_evasion_unusual_network_connection_via_dllhost.json'; +import rule538 from './defense_evasion_amsienable_key_mod.json'; +import rule539 from './persistence_via_bits_job_notify_command.json'; export const rawRules = [ rule1, @@ -1082,4 +1086,8 @@ export const rawRules = [ rule533, rule534, rule535, + rule536, + rule537, + rule538, + rule539, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json index b18ab4090b1f8..008f6ac7b874c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Azure Active Directory High Risk Sign-in", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.signinlogs and azure.signinlogs.properties.risk_level_during_signin:high and event.outcome:(success or Success)", + "query": "event.dataset:azure.signinlogs and\n azure.signinlogs.properties.risk_level_during_signin:high and\n event.outcome:(success or Success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-risk", "https://docs.microsoft.com/en-us/azure/active-directory/identity-protection/overview-identity-protection", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json index 7e77d851eac00..207d5534efd79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure Active Directory PowerShell Sign-in", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.signinlogs and azure.signinlogs.properties.app_display_name:\"Azure Active Directory PowerShell\" and azure.signinlogs.properties.token_issuer_type:AzureAD and event.outcome:(success or Success)", + "query": "event.dataset:azure.signinlogs and\n azure.signinlogs.properties.app_display_name:\"Azure Active Directory PowerShell\" and\n azure.signinlogs.properties.token_issuer_type:AzureAD and event.outcome:(success or Success)\n", "references": [ "https://msrc-blog.microsoft.com/2020/12/13/customer-guidance-on-recent-nation-state-cyber-attacks/", "https://docs.microsoft.com/en-us/microsoft-365/enterprise/connect-to-microsoft-365-powershell?view=o365-worldwide" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json index f73c30f55eb3a..45acc46fcd49d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Possible Consent Grant Attack via Azure-Registered Application", "note": "## Triage and analysis\n\n- In a consent grant attack, an attacker tricks an end user into granting a malicious application consent to access their data, usually via a phishing attack. After the malicious application has been granted consent, it has account-level access to data without the need for an organizational account.\n- Normal remediation steps, like resetting passwords for breached accounts or requiring Multi-Factor Authentication (MFA) on accounts, are not effective against this type of attack, since these are third-party applications and are external to the organization.\n- Security analysts should review the list of trusted applications for any suspicious items.\n\n\n## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(azure.activitylogs or azure.auditlogs or o365.audit) and ( azure.activitylogs.operation_name:\"Consent to application\" or azure.auditlogs.operation_name:\"Consent to application\" or o365.audit.Operation:\"Consent to application.\" ) and event.outcome:(Success or success)", + "query": "event.dataset:(azure.activitylogs or azure.auditlogs or o365.audit) and \n (\n azure.activitylogs.operation_name:\"Consent to application\" or\n azure.auditlogs.operation_name:\"Consent to application\" or\n o365.audit.Operation:\"Consent to application.\"\n ) and\n event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/detect-and-remediate-illicit-consent-grants?view=o365-worldwide" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json index 7d199f72a22bd..346f679b6c570 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS Management Console Root Login", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and aws.cloudtrail.user_identity.type:Root and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and aws.cloudtrail.user_identity.type:Root and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json index fa04b1f724ef7..70c3ea5de73f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Azure External Guest User Invitation", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Invite external user\" and azure.auditlogs.properties.target_resources.*.display_name:guest and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Invite external user\" and azure.auditlogs.properties.target_resources.*.display_name:guest and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/governance/policy/samples/cis-azure-1-1-0" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json index a823f162d405a..7cd94103c6395 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP IAM Custom Role Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateRole and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateRole and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/understanding-custom-roles" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json index 92b9bf7dc8090..18a201e7638ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json @@ -9,7 +9,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Auditd Max Failed Login Attempts", - "query": "event.module:auditd and event.action:\"failed-log-in-too-many-times-to\"", + "query": "event.module:auditd and event.action:\"failed-log-in-too-many-times-to\"\n", "references": [ "https://github.com/linux-pam/linux-pam/blob/0adbaeb273da1d45213134aa271e95987103281c/modules/pam_faillock/pam_faillock.c#L574" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json index f762779103ee1..3224836eb927a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json @@ -9,7 +9,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Auditd Login from Forbidden Location", - "query": "event.module:auditd and event.action:\"attempted-log-in-from-unusual-place-to\"", + "query": "event.module:auditd and event.action:\"attempted-log-in-from-unusual-place-to\"\n", "references": [ "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_access/pam_access.c#L412" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json index 09c389033ca07..b7a51feb122a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json @@ -9,7 +9,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Auditd Max Login Sessions", - "query": "event.module:auditd and event.action:\"opened-too-many-sessions-to\"", + "query": "event.module:auditd and event.action:\"opened-too-many-sessions-to\"\n", "references": [ "https://github.com/linux-pam/linux-pam/blob/70c32cc6fca51338f92afa58eb75b1107a5c2430/modules/pam_limits/pam_limits.c#L1007" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json index 9eacbca79708a..f5ab15fb442f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json @@ -9,7 +9,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Auditd Login Attempt at Forbidden Time", - "query": "event.module:auditd and event.action:\"attempted-log-in-during-unusual-hour-to\"", + "query": "event.module:auditd and event.action:\"attempted-log-in-during-unusual-hour-to\"\n", "references": [ "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_time/pam_time.c#L666" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json index 46c771e5b5ede..d98c6d1fb2260 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Anti-Phish Policy Deletion", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-AntiPhishPolicy\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Remove-AntiPhishPolicy\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-antiphishpolicy?view=exchange-ps", "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json index 471a010338085..554f5f80be246 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Anti-Phish Rule Modification", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-AntiPhishRule\" or \"Disable-AntiPhishRule\") and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Remove-AntiPhishRule\" or \"Disable-AntiPhishRule\") and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-antiphishrule?view=exchange-ps", "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-antiphishrule?view=exchange-ps" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json index d348d790f0c4e..0f936a91023f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Safe Link Policy Disabled", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeLinksRule\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Disable-SafeLinksRule\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/disable-safelinksrule?view=exchange-ps", "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/atp-safe-links?view=o365-worldwide" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json index fcbbcec5f06ac..9d0315ea692fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS IAM Password Recovery Requested", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:PasswordRecoveryRequested and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:PasswordRecoveryRequested and event.outcome:success\n", "references": [ "https://www.cadosecurity.com/2020/06/11/an-ongoing-aws-phishing-campaign/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json index 81c011cda898a..8e7f8ea6a842b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json @@ -12,7 +12,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "RPC (Remote Procedure Call) from the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:135 or event.dataset:zeek.dce_rpc) and not source.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" ) and destination.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:135 or event.dataset:zeek.dce_rpc) and\n not source.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n ) and\n destination.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n )\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 73, "rule_id": "143cb236-0956-4f42-a706-814bcaa0cf5a", "severity": "high", @@ -42,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json index 7d1a59886164a..5892bca71d5c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json @@ -12,7 +12,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "RPC (Remote Procedure Call) to the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:135 or event.dataset:zeek.dce_rpc) and source.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 ) and not destination.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:135 or event.dataset:zeek.dce_rpc) and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 73, "rule_id": "32923416-763a-4531-bb35-f33b9232ecdb", "severity": "high", @@ -42,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json index 60f488fe9d05f..b119dc0a4f211 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SMB (Windows File Sharing) Activity to the Internet", - "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:(139 or 445) or event.dataset:zeek.smb) and source.ip:( 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 ) and not destination.ip:( 10.0.0.0/8 or 127.0.0.0/8 or 169.254.0.0/16 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.0/4 or \"::1\" or \"FE80::/10\" or \"FF00::/8\" )", + "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:(139 or 445) or event.dataset:zeek.smb) and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.168.0.0/16 or\n 224.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n", "risk_score": 73, "rule_id": "c82b2bd8-d701-420c-ba43-f11a155b681a", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json index 03fb7e44b200d..abcc8d06444e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Suspicious Activity Reported by Okta User", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.account.report_suspicious_activity_by_enduser", + "query": "event.dataset:okta.system and event.action:user.account.report_suspicious_activity_by_enduser\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json index 41ab4386c6817..965d4a7f62369 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Inbound Connection to an Unsecure Elasticsearch Node", "note": "## Config\n\nThis rule requires the addition of port `9200` and `send_all_headers` to the `HTTP` protocol configuration in `packetbeat.yml`. See the References section for additional configuration documentation.", - "query": "event.category:network_traffic AND network.protocol:http AND status:OK AND destination.port:9200 AND network.direction:inbound AND NOT http.response.headers.content-type:\"image/x-icon\" AND NOT _exists_:http.request.headers.authorization", + "query": "event.category:network_traffic AND network.protocol:http AND status:OK AND destination.port:9200 AND network.direction:inbound AND NOT http.response.headers.content-type:\"image/x-icon\" AND NOT _exists_:http.request.headers.authorization\n", "references": [ "https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-security.html", "https://www.elastic.co/guide/en/beats/packetbeat/current/packetbeat-http-options.html#_send_all_headers" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json index 73370f48253a6..70fadcc29f648 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS Execution via System Manager", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ssm.amazonaws.com and event.action:SendCommand and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:ssm.amazonaws.com and event.action:SendCommand and event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-plugins.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json index 9ceb2d7143713..8c3ae88116a79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json @@ -10,7 +10,7 @@ "license": "Elastic License v2", "name": "Zoom Meeting with no Passcode", "note": "## Config\n\nThe Zoom Filebeat module or similarly structured data is required to be compatible with this rule.", - "query": "event.type:creation and event.module:zoom and event.dataset:zoom.webhook and event.action:meeting.created and not zoom.meeting.password:*", + "query": "event.type:creation and event.module:zoom and event.dataset:zoom.webhook and\n event.action:meeting.created and not zoom.meeting.password:*\n", "references": [ "https://blog.zoom.us/a-message-to-our-users/", "https://www.fbi.gov/contact-us/field-offices/boston/news/press-releases/fbi-warns-of-teleconferencing-and-online-classroom-hijacking-during-covid-19-pandemic" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json index e8f28ffd5e4c6..82fa9d8d72a92 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Kerberos Attack via Bifrost", - "query": "event.category:process and event.type:start and process.args:(\"-action\" and (\"-kerberoast\" or askhash or asktgs or asktgt or s4u or (\"-ticket\" and ptt) or (dump and (tickets or keytab))))", + "query": "event.category:process and event.type:start and \n process.args:(\"-action\" and (\"-kerberoast\" or askhash or asktgs or asktgt or s4u or (\"-ticket\" and ptt) or (dump and (tickets or keytab))))\n", "references": [ "https://github.com/its-a-feature/bifrost" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json index 51bf90ff1eaf0..b34badc7c8611 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Abnormally Large DNS Response", "note": "## Triage and analysis\n\n### Investigating Large DNS Responses\nDetection alerts from this rule indicate an attempt was made to exploit CVE-2020-1350 (SigRed) through the use of large DNS responses on a Windows DNS server. Here are some possible avenues of investigation:\n- Investigate any corresponding Intrusion Detection Signatures (IDS) alerts that can validate this detection alert.\n- Examine the `dns.question_type` network fieldset with a protocol analyzer, such as Zeek, Packetbeat, or Suricata, for `SIG` or `RRSIG` data.\n- Validate the patch level and OS of the targeted DNS server to validate the observed activity was not large-scale Internet vulnerability scanning.\n- Validate that the source of the network activity was not from an authorized vulnerability scan or compromise assessment.", - "query": "event.category:(network or network_traffic) and destination.port:53 and (event.dataset:zeek.dns or type:dns or event.type:connection) and network.bytes > 60000", + "query": "event.category:(network or network_traffic) and destination.port:53 and\n (event.dataset:zeek.dns or type:dns or event.type:connection) and network.bytes > 60000\n", "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", "https://msrc-blog.microsoft.com/2020/07/14/july-2020-security-update-cve-2020-1350-vulnerability-in-windows-domain-name-system-dns-server/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json index 37fe1a6a99791..dc4d84d497097 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Remote SSH Login Enabled via systemsetup Command", - "query": "event.category:process and event.type:(start or process_started) and process.name:systemsetup and process.args:(\"-setremotelogin\" and on)", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:systemsetup and\n process.args:(\"-setremotelogin\" and on)\n", "references": [ "https://documents.trendmicro.com/assets/pdf/XCSSET_Technical_Brief.pdf", "https://ss64.com/osx/systemsetup.html", 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 1c31fa440238a..73dca7c566421 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 @@ -14,7 +14,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Connection to External Network via Telnet", - "query": "sequence by process.entity_id\n [process where process.name == \"telnet\" and event.type == \"start\"]\n [network where process.name == \"telnet\" and\n not cidrmatch(destination.ip, \"127.0.0.0/8\", \"10.0.0.0/8\", \"172.16.0.0/12\",\n \"192.168.0.0/16\", \"FE80::/10\", \"::1/128\")]\n", + "query": "sequence by process.entity_id\n [process where process.name == \"telnet\" and event.type == \"start\"]\n [network where process.name == \"telnet\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 47, "rule_id": "e19e64ee-130e-4c07-961f-8a339f0b8362", "severity": "medium", @@ -43,5 +46,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } 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 9b4a031aa1609..31ca15c44a8bb 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 @@ -14,7 +14,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Connection to Internal Network via Telnet", - "query": "sequence by process.entity_id\n [process where process.name == \"telnet\" and event.type == \"start\"]\n [network where process.name == \"telnet\" and\n cidrmatch(destination.ip, \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\", \"FE80::/10\") and\n not cidrmatch(destination.ip, \"127.0.0.0/8\", \"::1/128\")]\n", + "query": "sequence by process.entity_id\n [process where process.name == \"telnet\" and event.type == \"start\"]\n [network where process.name == \"telnet\" and\n cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", + "references": [ + "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml" + ], "risk_score": 47, "rule_id": "1b21abcc-4d9f-4b08-a7f5-316f5f94b973", "severity": "medium", @@ -43,5 +46,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } 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 a706d2a117e7b..3a91b205e9da3 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Hping Process Activity", - "query": "event.category:process and event.type:(start or process_started) and process.name:(hping or hping2 or hping3)", + "query": "event.category:process and event.type:(start or process_started) and process.name:(hping or hping2 or hping3)\n", "references": [ "https://en.wikipedia.org/wiki/Hping" ], 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 cbd8c14052fee..d092c64098648 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential DNS Tunneling via Iodine", - "query": "event.category:process and event.type:(start or process_started) and process.name:(iodine or iodined)", + "query": "event.category:process and event.type:(start or process_started) and process.name:(iodine or iodined)\n", "references": [ "https://code.kryo.se/iodine/" ], 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 94c37610abf2e..c9898b7414c09 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Nping Process Activity", - "query": "event.category:process and event.type:(start or process_started) and process.name:nping", + "query": "event.category:process and event.type:(start or process_started) and process.name:nping\n", "references": [ "https://en.wikipedia.org/wiki/Nmap" ], 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 7b3c8af056f18..8b71c053fc6d6 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Unusual Process Execution - Temp", - "query": "event.category:process and event.type:(start or process_started) and process.working_directory:/tmp", + "query": "event.category:process and event.type:(start or process_started) and process.working_directory:/tmp\n", "risk_score": 47, "rule_id": "df959768-b0c9-4d45-988c-5606a2be8e5a", "severity": "medium", 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 d14db4869a8f7..210a71ced9222 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Strace Process Activity", - "query": "event.category:process and event.type:(start or process_started) and process.name:strace", + "query": "event.category:process and event.type:(start or process_started) and process.name:strace\n", "references": [ "https://en.wikipedia.org/wiki/Strace" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json index b393fa13db8c7..db8ea7369d456 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "MFA Disabled for Google Workspace Organization", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:(ENFORCE_STRONG_AUTHENTICATION or ALLOW_STRONG_AUTHENTICATION) and gsuite.admin.new_value:false", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:(ENFORCE_STRONG_AUTHENTICATION or ALLOW_STRONG_AUTHENTICATION) and gsuite.admin.new_value:false\n", "risk_score": 47, "rule_id": "e555105c-ba6d-481f-82bb-9b633e7b4827", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json index 280d40537e448..6933a81a22944 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_exchange_dkim_signing_config_disabled.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange DKIM Signing Configuration Disabled", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Set-DkimSigningConfig\" and o365.audit.Parameters.Enabled:False and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"Set-DkimSigningConfig\" and o365.audit.Parameters.Enabled:False and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/set-dkimsigningconfig?view=exchange-ps" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json index cb87e9f7cb257..add3495d03271 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/microsoft_365_teams_custom_app_interaction_allowed.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Teams Custom Application Interaction Allowed", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:MicrosoftTeams and event.category:web and event.action:TeamsTenantSettingChanged and o365.audit.Name:\"Allow sideloading and interaction of custom apps\" and o365.audit.NewValue:True and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:MicrosoftTeams and\nevent.category:web and event.action:TeamsTenantSettingChanged and\no365.audit.Name:\"Allow sideloading and interaction of custom apps\" and\no365.audit.NewValue:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json index 7856d13b8d66f..f0d999476bb42 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json @@ -9,8 +9,8 @@ ], "from": "now-30m", "interval": "15m", - "license": "Elastic License", - "machine_learning_job_id": "high-count-network-denies", + "license": "Elastic License v2", + "machine_learning_job_id": "high_count_network_denies", "name": "Spike in Firewall Denies", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json index 14aec268cc13b..39ded30776bad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json @@ -9,8 +9,8 @@ ], "from": "now-30m", "interval": "15m", - "license": "Elastic License", - "machine_learning_job_id": "high-count-network-events", + "license": "Elastic License v2", + "machine_learning_job_id": "high_count_network_events", "name": "Spike in Network Traffic", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json index 1c886d0457afc..a2638f60d7495 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_rare_metadata_process", + "machine_learning_job_id": [ + "linux_rare_metadata_process", + "v2_linux_rare_metadata_process" + ], "name": "Unusual Linux Process Calling the Metadata Service", "risk_score": 21, "rule_id": "9d302377-d226-4e12-b54c-1906b5aec4f6", @@ -23,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json index 984060951b3c5..c176bf0d7ad68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_rare_metadata_user", + "machine_learning_job_id": [ + "linux_rare_metadata_user", + "v2_linux_rare_metadata_user" + ], "name": "Unusual Linux User Calling the Metadata Service", "risk_score": 21, "rule_id": "1faec04b-d902-4f89-8aff-92cd9043c16f", @@ -23,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json index c56235774c077..eb6a960702ac7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_network_port_activity_ecs", + "machine_learning_job_id": [ + "linux_anomalous_network_port_activity_ecs", + "v2_linux_anomalous_network_port_activity_ecs" + ], "name": "Unusual Linux Network Port Activity", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json index 9243389de0d97..bab02f0a6aa24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_process_all_hosts_ecs", + "machine_learning_job_id": [ + "linux_anomalous_process_all_hosts_ecs", + "v2_linux_anomalous_process_all_hosts_ecs" + ], "name": "Anomalous Process For a Linux Population", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux Process\nDetection alerts from this rule indicate the presence of a Linux process that is rare and unusual for all of the monitored Linux hosts for which Auditbeat data is available. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json index e77247dc0e931..4eb10707e0eb2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_user_name_ecs", + "machine_learning_job_id": [ + "linux_anomalous_user_name_ecs", + "v2_linux_anomalous_user_name_ecs" + ], "name": "Unusual Linux Username", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux User\nDetection alerts from this rule indicate activity for a Linux user name that is rare and unusual. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host? Could this be related to troubleshooting or debugging activity by a developer or site reliability engineer?\n- Examine the history of user activity. If this user manifested only very recently, it might be a service account for a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user is performing.", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json index 571c7e0d0d32c..8c8b7ffbd12ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json @@ -9,8 +9,8 @@ ], "from": "now-30m", "interval": "15m", - "license": "Elastic License", - "machine_learning_job_id": "rare-destination-country", + "license": "Elastic License v2", + "machine_learning_job_id": "rare_destination_country", "name": "Network Traffic to Rare Destination Country", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json index 11f6c2605f6ad..934a5e598629b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "rare_process_by_host_linux_ecs", + "machine_learning_job_id": [ + "rare_process_by_host_linux_ecs", + "v2_rare_process_by_host_linux_ecs" + ], "name": "Unusual Process For a Linux Host", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux Process\nDetection alerts from this rule indicate the presence of a Linux process that is rare and unusual for the host it ran on. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json index 72db8ff93d4d4..3373f51b69db0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "rare_process_by_host_windows_ecs", + "machine_learning_job_id": [ + "rare_process_by_host_windows_ecs", + "v2_rare_process_by_host_windows_ecs" + ], "name": "Unusual Process For a Windows Host", "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\nDetection alerts from this rule indicate the presence of a Windows process that is rare and unusual for the host it ran on. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json index e1e571bbd1c99..1b6c71ed0b0bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json @@ -9,8 +9,8 @@ ], "from": "now-30m", "interval": "15m", - "license": "Elastic License", - "machine_learning_job_id": "high-count-by-destination-country", + "license": "Elastic License v2", + "machine_learning_job_id": "high_count_by_destination_country", "name": "Spike in Network Traffic To a Country", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json index d19897057497c..deaf07a77c7be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_rare_metadata_process", + "machine_learning_job_id": [ + "windows_rare_metadata_process", + "v2_windows_rare_metadata_process" + ], "name": "Unusual Windows Process Calling the Metadata Service", "risk_score": 21, "rule_id": "abae61a8-c560-4dbd-acca-1e1438bff36b", @@ -23,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json index 27f100c4e65b8..31e078b795256 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_rare_metadata_user", + "machine_learning_job_id": [ + "windows_rare_metadata_user", + "v2_windows_rare_metadata_user" + ], "name": "Unusual Windows User Calling the Metadata Service", "risk_score": 21, "rule_id": "df197323-72a8-46a9-a08e-3f5b04a4a97a", @@ -23,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json index 5e3d5757b160d..dbee5dd256873 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_network_activity_ecs", + "machine_learning_job_id": [ + "windows_anomalous_network_activity_ecs", + "v2_windows_anomalous_network_activity_ecs" + ], "name": "Unusual Windows Network Activity", "note": "## Triage and analysis\n\n### Investigating Unusual Network Activity\nDetection alerts from this rule indicate the presence of network activity from a Windows process for which network activity is very unusual. Here are some possible avenues of investigation:\n- Consider the IP addresses, protocol and ports. Are these used by normal but infrequent network workflows? Are they expected or unexpected?\n- If the destination IP address is remote or external, does it associate with an expected domain, organization or geography? Note: avoid interacting directly with suspected malicious IP addresses.\n- Consider the user as identified by the username field. Is this network activity part of an expected workflow for the user who ran the program?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools.", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json index 93a33e41e6ce9..782a80c53f9b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_path_activity_ecs", + "machine_learning_job_id": [ + "windows_anomalous_path_activity_ecs", + "v2_windows_anomalous_path_activity_ecs" + ], "name": "Unusual Windows Path Activity", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json index b73637c1a7cdf..09acb2121fd5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_process_all_hosts_ecs", + "machine_learning_job_id": [ + "windows_anomalous_process_all_hosts_ecs", + "v2_windows_anomalous_process_all_hosts_ecs" + ], "name": "Anomalous Process For a Windows Population", "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\nDetection alerts from this rule indicate the presence of a Windows process that is rare and unusual for all of the Windows hosts for which Winlogbeat data is available. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process manifested only very recently, it might be part of a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json index 7b3a028f1b371..cfe9e7ff1eaa3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_process_creation", + "machine_learning_job_id": [ + "windows_anomalous_process_creation", + "v2_windows_anomalous_process_creation" + ], "name": "Anomalous Windows Process Creation", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json index d3cbc34bd1a3d..b2183c8ff66c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json @@ -10,7 +10,10 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_user_name_ecs", + "machine_learning_job_id": [ + "windows_anomalous_user_name_ecs", + "v2_windows_anomalous_user_name_ecs" + ], "name": "Unusual Windows Username", "note": "## Triage and analysis\n\n### Investigating an Unusual Windows User\nDetection alerts from this rule indicate activity for a Windows user name that is rare and unusual. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host? Could this be related to occasional troubleshooting or support activity?\n- Examine the history of user activity. If this user manifested only very recently, it might be a service account for a new software package. If it has a consistent cadence - for example if it runs monthly or quarterly - it might be part of a monthly or quarterly business process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.", "references": [ @@ -27,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts index cad41391e2b42..86d95c1e021b4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts @@ -2,17 +2,17 @@ /* @notice * Detection Rules - * Copyright 2020 Elasticsearch B.V. + * Copyright 2021 Elasticsearch B.V. * * --- * This product bundles rules based on https://github.com/BlueTeamLabs/sentinel-attack - * which is available under a "MIT" license. The files based on this license are: + * which is available under a "MIT" license. The rules based on this license are: * - * - defense_evasion_via_filter_manager - * - discovery_process_discovery_via_tasklist_command - * - persistence_priv_escalation_via_accessibility_features - * - persistence_via_application_shimming - * - defense_evasion_execution_via_trusted_developer_utilities + * - "Potential Evasion via Filter Manager" (06dceabf-adca-48af-ac79-ffdf4c3b1e9a) + * - "Process Discovery via Tasklist" (cc16f774-59f9-462d-8b98-d27ccd4519ec) + * - "Potential Modification of Accessibility Binaries" (7405ddf1-6c8e-41ce-818f-48bea6bcaed8) + * - "Potential Application Shimming via Sdbinst" (fd4a992d-6130-4802-9ff8-829b89ae801f) + * - "Trusted Developer Application Usage" (9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae1) * * MIT License * @@ -38,9 +38,9 @@ * * --- * This product bundles rules based on https://github.com/FSecureLABS/leonidas - * which is available under a "MIT" license. The files based on this license are: + * which is available under a "MIT" license. The rules based on this license are: * - * - credential_access_secretsmanager_getsecretvalue.toml + * - "AWS Access Secret in Secrets Manager" (a00681e3-9ed6-447c-ab2c-be648821c622) * * MIT License * diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_application.json index bb4cdc6ded201..2c3512fb2ad77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_application.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Deactivate an Okta Application", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:application.lifecycle.deactivate", + "query": "event.dataset:okta.system and event.action:application.lifecycle.deactivate\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Apps/Apps_Apps.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy.json index 54dd1f0741488..45605a297987b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Deactivate an Okta Policy", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.lifecycle.deactivate", + "query": "event.dataset:okta.system and event.action:policy.lifecycle.deactivate\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy_rule.json index 495404ac9e108..ba25dde279426 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_deactivate_okta_policy_rule.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Deactivate an Okta Policy Rule", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.rule.deactivate", + "query": "event.dataset:okta.system and event.action:policy.rule.deactivate\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_application.json index 950964c81458a..4f18d2496e874 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_application.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Delete an Okta Application", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:application.lifecycle.delete", + "query": "event.dataset:okta.system and event.action:application.lifecycle.delete\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy.json index 43b146b1377b7..47660e2d5d73e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Delete an Okta Policy", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.lifecycle.delete", + "query": "event.dataset:okta.system and event.action:policy.lifecycle.delete\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy_rule.json index 3e32480d958ea..d865bda9c3a6f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_delete_okta_policy_rule.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Delete an Okta Policy Rule", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.rule.delete", + "query": "event.dataset:okta.system and event.action:policy.rule.delete\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_application.json index 47c0c836348d6..3c0524749eed9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_application.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Modify an Okta Application", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:application.lifecycle.update", + "query": "event.dataset:okta.system and event.action:application.lifecycle.update\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Apps/Apps_Apps.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_network_zone.json index 05e48c9175daf..de94a52a4caa4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_network_zone.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_network_zone.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Modify an Okta Network Zone", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:(zone.update or network_zone.rule.disabled or zone.remove_blacklist)", + "query": "event.dataset:okta.system and event.action:(zone.update or network_zone.rule.disabled or zone.remove_blacklist)\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy.json index 78451913b110a..768d69341e71a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Modify an Okta Policy", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.lifecycle.update", + "query": "event.dataset:okta.system and event.action:policy.lifecycle.update\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy_rule.json index 3d89183e4a921..cae49f5789b29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_okta_policy_rule.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Modify an Okta Policy Rule", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:policy.rule.update", + "query": "event.dataset:okta.system and event.action:policy.rule.update\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_or_delete_application_sign_on_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_or_delete_application_sign_on_policy.json index ce68423ffa4a4..20e52ad1aca7b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_or_delete_application_sign_on_policy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_attempt_to_modify_or_delete_application_sign_on_policy.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Modification or Removal of an Okta Application Sign-On Policy", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:(application.policy.sign_on.update or application.policy.sign_on.rule.delete)", + "query": "event.dataset:okta.system and event.action:(application.policy.sign_on.update or application.policy.sign_on.rule.delete)\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/App_Based_Signon.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json index 51be6cc2aba45..c2225b53e30b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Threat Detected by Okta ThreatInsight", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:security.threat.detected", + "query": "event.dataset:okta.system and event.action:security.threat.detected\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json index faf309fac1365..dc8a5ba10fa00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Hidden Local User Account Creation", - "query": "event.category:process and event.type:(start or process_started) and process.name:dscl and process.args:(IsHidden and create and (true or 1 or yes))", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:dscl and process.args:(IsHidden and create and (true or 1 or yes))\n", "references": [ "https://support.apple.com/en-us/HT203998" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json index 2284e08ba0caa..071fb643083a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Administrator Privileges Assigned to an Okta Group", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:group.privilege.grant", + "query": "event.dataset:okta.system and event.action:group.privilege.grant\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/administrators-admin-comparison.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json index 8aa425e4faac6..eccd892913023 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Administrator Role Assigned to an Okta User", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.account.privilege.grant", + "query": "event.dataset:okta.system and event.action:user.account.privilege.grant\n", "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/administrators-admin-comparison.htm", "https://developer.okta.com/docs/reference/api/system-log/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json index df234db46fed2..02cc638a7ce0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Create Okta API Token", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:system.api_token.create", + "query": "event.dataset:okta.system and event.action:system.api_token.create\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json index df70b91b8c8a0..29d604a2953ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Deactivate MFA for an Okta User Account", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.mfa.factor.deactivate", + "query": "event.dataset:okta.system and event.action:user.mfa.factor.deactivate\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json index a9bbbca91e2b7..2a2f5acfcc114 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Attempt to Reset MFA Factors for an Okta User Account", "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:okta.system and event.action:user.mfa.factor.reset_all", + "query": "event.dataset:okta.system and event.action:user.mfa.factor.reset_all\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", "https://developer.okta.com/docs/reference/api/event-types/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json index 5ec70f2970d28..bdae2f42bd04e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Automation Account Created", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WRITE\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WRITE\" and event.outcome:(Success or success)\n", "references": [ "https://powerzure.readthedocs.io/en/latest/Functions/operational.html#create-backdoor", "https://github.com/hausec/PowerZure", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json index f7ef529895222..0c6a82d37701b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Automation Runbook Created or Modified", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name: ( \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DRAFT/WRITE\" or \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/WRITE\" or \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/PUBLISH/ACTION\" ) and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and\n azure.activitylogs.operation_name:\n (\n \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/DRAFT/WRITE\" or\n \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/WRITE\" or\n \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/RUNBOOKS/PUBLISH/ACTION\"\n ) and\n event.outcome:(Success or success)\n", "references": [ "https://powerzure.readthedocs.io/en/latest/Functions/operational.html#create-backdoor", "https://github.com/hausec/PowerZure", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json index e77d1f91747e7..2b8110b8e7d54 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Automation Webhook Created", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name: ( \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WEBHOOKS/ACTION\" or \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WEBHOOKS/WRITE\" ) and event.outcome:(Success or success)", + "query": "event.dataset:azure.activitylogs and\n azure.activitylogs.operation_name:\n (\n \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WEBHOOKS/ACTION\" or\n \"MICROSOFT.AUTOMATION/AUTOMATIONACCOUNTS/WEBHOOKS/WRITE\"\n ) and\n event.outcome:(Success or success)\n", "references": [ "https://powerzure.readthedocs.io/en/latest/Functions/operational.html#create-backdoor", "https://github.com/hausec/PowerZure", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json index 00826c3d6162e..700ae78685257 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Conditional Access Policy Modified", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(azure.activitylogs or azure.auditlogs) and ( azure.activitylogs.operation_name:\"Update policy\" or azure.auditlogs.operation_name:\"Update policy\" ) and event.outcome:(Success or success)", + "query": "event.dataset:(azure.activitylogs or azure.auditlogs) and\n (\n azure.activitylogs.operation_name:\"Update policy\" or\n azure.auditlogs.operation_name:\"Update policy\"\n ) and\n event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/overview" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json index 1d5332beed4f6..4ee9f8074985b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "Azure Global Administrator Role Addition to PIM User", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.properties.category:RoleManagement and azure.auditlogs.operation_name:(\"Add eligible member to role in PIM completed (permanent)\" or \"Add member to role in PIM completed (timebound)\") and azure.auditlogs.properties.target_resources.*.display_name:\"Global Administrator\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.properties.category:RoleManagement and\n azure.auditlogs.operation_name:(\"Add eligible member to role in PIM completed (permanent)\" or\n \"Add member to role in PIM completed (timebound)\") and\n azure.auditlogs.properties.target_resources.*.display_name:\"Global Administrator\" and\n event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/directory-assign-admin-roles" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json index 0a75c0feaa516..214100ffced5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Azure Privilege Identity Management Role Modified", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Update role setting in PIM\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Update role setting in PIM\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-resource-roles-assign-roles", "https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json index 3df0e48eb5ee3..d66066bca644e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Authorization Plugin Modification", - "query": "event.category:file and not event.type:deletion and file.path:(/Library/Security/SecurityAgentPlugins/* and not /Library/Security/SecurityAgentPlugins/TeamViewerAuthPlugin.bundle/Contents/*)", + "query": "event.category:file and not event.type:deletion and\n file.path:(/Library/Security/SecurityAgentPlugins/* and\n not /Library/Security/SecurityAgentPlugins/TeamViewerAuthPlugin.bundle/Contents/*)\n", "references": [ "https://developer.apple.com/documentation/security/authorization_plug-ins", "https://www.xorrior.com/persistent-credential-theft/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json index e1d8c05438b81..31e52590e22e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Modification of Standard Authentication Module or Configuration", - "query": "event.category:file and event.type:change and (file.name:pam_*.so or file.path:(/etc/pam.d/* or /private/etc/pam.d/*)) and process.executable: (* and not ( /bin/yum or \"/usr/sbin/pam-auth-update\" or /usr/libexec/packagekitd or /usr/bin/dpkg or /usr/bin/vim or /usr/libexec/xpcproxy or /usr/bin/bsdtar or /usr/local/bin/brew or /usr/bin/rsync or /usr/bin/yum or /var/lib/docker/*/bin/yum or /var/lib/docker/*/bin/dpkg or ./merged/var/lib/docker/*/bin/dpkg or \"/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/XPCServices/package_script_service.xpc/Contents/MacOS/package_script_service\" ) ) and not file.path: ( /tmp/snap.rootfs_*/pam_*.so or /tmp/newroot/lib/*/pam_*.so or /private/var/folders/*/T/com.apple.fileprovider.ArchiveService/TemporaryItems/*/lib/security/pam_*.so or /tmp/newroot/usr/lib64/security/pam_*.so )", + "query": "event.category:file and event.type:change and \n (file.name:pam_*.so or file.path:(/etc/pam.d/* or /private/etc/pam.d/*)) and \n process.executable:\n (* and \n not \n (\n /bin/yum or \n \"/usr/sbin/pam-auth-update\" or \n /usr/libexec/packagekitd or \n /usr/bin/dpkg or \n /usr/bin/vim or \n /usr/libexec/xpcproxy or \n /usr/bin/bsdtar or \n /usr/local/bin/brew or\n /usr/bin/rsync or\n /usr/bin/yum or\n /var/lib/docker/*/bin/yum or\n /var/lib/docker/*/bin/dpkg or\n ./merged/var/lib/docker/*/bin/dpkg or\n \"/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/XPCServices/package_script_service.xpc/Contents/MacOS/package_script_service\"\n )\n ) and\n not file.path:\n (\n /tmp/snap.rootfs_*/pam_*.so or\n /tmp/newroot/lib/*/pam_*.so or\n /private/var/folders/*/T/com.apple.fileprovider.ArchiveService/TemporaryItems/*/lib/security/pam_*.so or\n /tmp/newroot/usr/lib64/security/pam_*.so\n )\n", "references": [ "https://github.com/zephrax/linux-pam-backdoor", "https://github.com/eurialo/pambd", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json index e9f4aeea01129..46414c400ab8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Modification of OpenSSH Binaries", - "query": "event.category:file and event.type:change and process.name:* and (file.path:(/usr/sbin/sshd or /usr/bin/ssh or /usr/bin/sftp or /usr/bin/scp) or file.name:libkeyutils.so) and not process.executable:/usr/bin/dpkg", + "query": "event.category:file and event.type:change and \n process.name:* and\n (file.path:(/usr/sbin/sshd or /usr/bin/ssh or /usr/bin/sftp or /usr/bin/scp) or file.name:libkeyutils.so) and\n not process.executable:/usr/bin/dpkg\n", "references": [ "https://blog.angelalonso.es/2016/09/anatomy-of-real-linux-intrusion-part-ii.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json index 9778691f1d2f3..7ff8f12e60011 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Hidden Child Process of Launchd", - "query": "event.category:process and event.type:(start or process_started) and process.name:.* and process.parent.executable:/sbin/launchd", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:.* and process.parent.executable:/sbin/launchd\n", "references": [ "https://objective-see.com/blog/blog_0x61.html", "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json index 18595dfe18048..0fce2b7647df4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Persistence via DirectoryService Plugin Modification", - "query": "event.category:file and not event.type:deletion and file.path:/Library/DirectoryServices/PlugIns/*.dsplug", + "query": "event.category:file and not event.type:deletion and\n file.path:/Library/DirectoryServices/PlugIns/*.dsplug\n", "references": [ "https://blog.chichou.me/2019/11/21/two-macos-persistence-tricks-abusing-plugins/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json index 6ccc3dbe509f2..941fe5cbf5484 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Persistence via Docker Shortcut Modification", - "query": "event.category : file and event.action : modification and file.path : /Users/*/Library/Preferences/com.apple.dock.plist and not process.name : (xpcproxy or cfprefsd or plutil or jamf or PlistBuddy or InstallerRemotePluginService)", + "query": "event.category : file and event.action : modification and \n file.path : /Users/*/Library/Preferences/com.apple.dock.plist and \n not process.name : (xpcproxy or cfprefsd or plutil or jamf or PlistBuddy or InstallerRemotePluginService)\n", "references": [ "https://github.com/specterops/presentations/raw/master/Leo Pitt/Hey_Im_Still_in_Here_Modern_macOS_Persistence_SO-CON2020.pdf" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json index 79ff9080c8f23..a545ae77d03ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS EC2 Network Access Control List Creation", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:(CreateNetworkAcl or CreateNetworkAclEntry) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and event.action:(CreateNetworkAcl or CreateNetworkAclEntry) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/create-network-acl.html", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateNetworkAcl.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json index 5d608649abe21..b81496a79f960 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Attempt to Enable the Root Account", - "query": "event.category:process and event.type:(start or process_started) and process.name:dsenableroot and not process.args:\"-d\"", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:dsenableroot and not process.args:\"-d\"\n", "references": [ "https://ss64.com/osx/dsenableroot.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json index 34efe6e0b3d0e..07372389f48c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP IAM Service Account Key Deletion", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteServiceAccountKey and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.DeleteServiceAccountKey and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/service-accounts", "https://cloud.google.com/iam/docs/creating-managing-service-account-keys" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json index 2c84a68b7603c..88249ad30adb9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Service Account Key Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateServiceAccountKey and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateServiceAccountKey and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/service-accounts", "https://cloud.google.com/iam/docs/creating-managing-service-account-keys" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json index 54d4c01c66ea4..d37cc80d562cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json @@ -14,7 +14,7 @@ "license": "Elastic License v2", "name": "GCP Service Account Creation", "note": "## Config\n\nThe GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateServiceAccount and event.outcome:success", + "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:google.iam.admin.v*.CreateServiceAccount and event.outcome:success\n", "references": [ "https://cloud.google.com/iam/docs/service-accounts" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json index 681d9f4f7d4c8..1ad3e0afeed52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace Admin Role Assigned to a User", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ASSIGN_ROLE", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:ASSIGN_ROLE\n", "references": [ "https://support.google.com/a/answer/172176?hl=en" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json index 819c047a2c64f..19dd54c6ccb35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace API Access Granted via Domain-Wide Delegation of Authority", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:AUTHORIZE_API_CLIENT_ACCESS", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:AUTHORIZE_API_CLIENT_ACCESS\n", "references": [ "https://developers.google.com/admin-sdk/directory/v1/guides/delegation" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json index 2a68fc2604f8b..ae03288800adc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace Custom Admin Role Created", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:CREATE_ROLE", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:CREATE_ROLE\n", "references": [ "https://support.google.com/a/answer/2406043?hl=en" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json index daf5b9b97a522..75bd229efa31c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "Google Workspace Role Modified", "note": "## Config\n\nThe Google Workspace Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n### Important Information Regarding Google Workspace Event Lag Times\n- As per Google's documentation, Google Workspace administrators may observe lag times ranging from minutes up to 3 days between the time of an event's occurrence and the event being visible in the Google Workspace admin/audit logs.\n- This rule is configured to run every 10 minutes with a lookback time of 130 minutes.\n- To reduce the risk of false negatives, consider reducing the interval that the Google Workspace (formerly G Suite) Filebeat module polls Google's reporting API for new events.\n- By default, `var.interval` is set to 2 hours (2h). Consider changing this interval to a lower value, such as 10 minutes (10m).\n- See the following references for further information.\n - https://support.google.com/a/answer/7061566\n - https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-gsuite.html", - "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:(ADD_PRIVILEGE or UPDATE_ROLE)", + "query": "event.dataset:(gsuite.admin or google_workspace.admin) and event.provider:admin and event.category:iam and event.action:(ADD_PRIVILEGE or UPDATE_ROLE)\n", "references": [ "https://support.google.com/a/answer/2406043?hl=en" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json index 0930201f0422e..084ccb4da74f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS IAM Group Creation", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:CreateGroup and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:CreateGroup and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-group.html", "https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateGroup.html" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json index d6ffaa532a530..0ec8607f4d71d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Potential Persistence via Login Hook", "note": "## Triage and analysis\n\nStarting in Mac OS X 10.7 (Lion), users can specify certain applications to be re-opened when a user reboots their machine. This can be abused to establish or maintain persistence on a compromised system.", - "query": "event.category:\"file\" and not event.type:\"deletion\" and file.name:\"com.apple.loginwindow.plist\" and process.name:(* and not (systemmigrationd or DesktopServicesHelper or diskmanagementd or rsync or launchd or cfprefsd or xpcproxy or ManagedClient or MCXCompositor))", + "query": "event.category:\"file\" and not event.type:\"deletion\" and\n file.name:\"com.apple.loginwindow.plist\" and\n process.name:(* and not (systemmigrationd or DesktopServicesHelper or diskmanagementd or rsync or launchd or cfprefsd or xpcproxy or ManagedClient or MCXCompositor))\n", "references": [ "https://github.com/D00MFist/PersistentJXA/blob/master/LoginScript.js" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json index e13975684a722..2514b5790d023 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Multi-Factor Authentication Disabled for an Azure User", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Disable Strong Authentication\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Disable Strong Authentication\" and event.outcome:(Success or success)\n", "risk_score": 47, "rule_id": "dafa3235-76dc-40e2-9f71-1773b96d24cf", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json index a0e51fa3b8eed..b145642a01968 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Exchange Management Group Role Assignment", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-ManagementRoleAssignment\" and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:\"New-ManagementRoleAssignment\" and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/exchange/new-managementroleassignment?view=exchange-ps", "https://docs.microsoft.com/en-us/microsoft-365/admin/add-users/about-admin-roles?view=o365-worldwide" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json index 570ac0323dfd1..f3ad4d22cf14c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Teams External Access Enabled", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and event.category:web and event.action:\"Set-CsTenantFederationConfiguration\" and o365.audit.Parameters.AllowFederatedUsers:True and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and\nevent.category:web and event.action:\"Set-CsTenantFederationConfiguration\" and\no365.audit.Parameters.AllowFederatedUsers:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoftteams/manage-external-access" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json index 492c3dcd5bc9d..93f72b401d51e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Teams Guest Access Enabled", "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and event.category:web and event.action:\"Set-CsTeamsClientConfiguration\" and o365.audit.Parameters.AllowGuestUser:True and event.outcome:success", + "query": "event.dataset:o365.audit and event.provider:(SkypeForBusiness or MicrosoftTeams) and\nevent.category:web and event.action:\"Set-CsTeamsClientConfiguration\" and\no365.audit.Parameters.AllowGuestUser:True and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/skype/get-csteamsclientconfiguration?view=skype-ps" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json index f48ea64022ecb..2df59206645b4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Persistence via Periodic Tasks", - "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:(/private/etc/periodic/* or /private/etc/defaults/periodic.conf or /private/etc/periodic.conf)", + "query": "event.category:\"file\" and not event.type:\"deletion\" and\n file.path:(/private/etc/periodic/* or /private/etc/defaults/periodic.conf or /private/etc/periodic.conf)\n", "references": [ "https://opensource.apple.com/source/crontabs/crontabs-13/private/etc/defaults/periodic.conf.auto.html", "https://www.oreilly.com/library/view/mac-os-x/0596003706/re328.html", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json index 3a13c02a368ad..4f28f277d21e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS RDS Cluster Creation", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(CreateDBCluster or CreateGlobalCluster) and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:(CreateDBCluster or CreateGlobalCluster) and event.outcome:success\n", "references": [ "https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/create-db-cluster.html", "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBCluster.html", 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 545787d7ec999..eec7157c74148 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 @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Shell via Web Server", - "query": "event.category:process and event.type:(start or process_started) and process.name:(bash or dash) and user.name:(apache or nginx or www or \"www-data\")", + "query": "event.category:process and event.type:(start or process_started) and process.name:(bash or dash) and\n user.name:(apache or nginx or www or \"www-data\")\n", "references": [ "https://pentestlab.blog/tag/web-shell/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json index fa56d07b190cd..41ca64fb6c162 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Bash Shell Profile Modification", - "query": "event.category:file and event.type:change and process.name:(* and not (sudo or vim or zsh or env or nano or bash or Terminal or xpcproxy or login or cat or cp or launchctl or java)) and not process.executable:(/Applications/* or /private/var/folders/* or /usr/local/*) and file.path:(/private/etc/rc.local or /etc/rc.local or /home/*/.profile or /home/*/.profile1 or /home/*/.bash_profile or /home/*/.bash_profile1 or /home/*/.bashrc or /Users/*/.bash_profile or /Users/*/.zshenv)", + "query": "event.category:file and event.type:change and\n process.name:(* and not (sudo or\n vim or\n zsh or\n env or\n nano or\n bash or\n Terminal or\n xpcproxy or\n login or\n cat or\n cp or\n launchctl or\n java)) and\n not process.executable:(/Applications/* or /private/var/folders/* or /usr/local/*) and\n file.path:(/private/etc/rc.local or\n /etc/rc.local or\n /home/*/.profile or\n /home/*/.profile1 or\n /home/*/.bash_profile or\n /home/*/.bash_profile1 or\n /home/*/.bashrc or\n /Users/*/.bash_profile or\n /Users/*/.zshenv)\n", "references": [ "https://www.anomali.com/blog/pulling-linux-rabbit-rabbot-malware-out-of-a-hat" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json index 46358ec7f47fe..23d16f121921b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SSH Authorized Keys File Modification", - "query": "event.category:file and event.type:(change or creation) and file.name:(\"authorized_keys\" or \"authorized_keys2\") and not process.executable: (/Library/Developer/CommandLineTools/usr/bin/git or /usr/local/Cellar/maven/*/libexec/bin/mvn or /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or /usr/bin/vim or /usr/local/Cellar/coreutils/*/bin/gcat or /usr/bin/bsdtar or /usr/bin/nautilus or /usr/bin/scp or /usr/bin/touch or /var/lib/docker/*)", + "query": "event.category:file and event.type:(change or creation) and \n file.name:(\"authorized_keys\" or \"authorized_keys2\") and \n not process.executable:\n (/Library/Developer/CommandLineTools/usr/bin/git or \n /usr/local/Cellar/maven/*/libexec/bin/mvn or \n /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or \n /usr/bin/vim or \n /usr/local/Cellar/coreutils/*/bin/gcat or \n /usr/bin/bsdtar or\n /usr/bin/nautilus or \n /usr/bin/scp or\n /usr/bin/touch or \n /var/lib/docker/*)\n", "risk_score": 47, "rule_id": "2215b8bd-1759-4ffa-8ab8-55c8e6b32e7f", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json index 1f0186c39ad26..97a3de8f4060d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Calendar File Modification", - "query": "event.category:file and event.action:modification and file.path:/Users/*/Library/Calendars/*.calendar/Events/*.ics and process.executable: (* and not ( /System/Library/* or /System/Applications/Calendar.app/Contents/MacOS/* or /usr/libexec/xpcproxy or /sbin/launchd or /Applications/* ) )", + "query": "event.category:file and event.action:modification and\n file.path:/Users/*/Library/Calendars/*.calendar/Events/*.ics and\n process.executable:\n (* and not \n (\n /System/Library/* or \n /System/Applications/Calendar.app/Contents/MacOS/* or \n /usr/libexec/xpcproxy or \n /sbin/launchd or \n /Applications/*\n )\n )\n", "references": [ "https://labs.f-secure.com/blog/operationalising-calendar-alerts-persistence-on-macos", "https://github.com/FSecureLABS/CalendarPersist", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index f20b00d057f1a..b0f573730b73f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Component Object Model Hijacking", - "query": "registry where\n /* uncomment once length is stable length(bytes_written_string) > 0 and */\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\") \n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocXServer32\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\")\n", + "query": "registry where\n /* uncomment once length is stable length(bytes_written_string) > 0 and */\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\") \n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\")\n", "references": [ "https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/" ], @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json index ee2fdb5b75eac..1d0990e951925 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "User Added as Owner for Azure Application", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add owner to application\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add owner to application\" and event.outcome:(Success or success)\n", "risk_score": 21, "rule_id": "774f5e28-7b75-4a58-b94e-41bf060fdd86", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json index aacc17bb6be53..b68b4826bc4cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "User Added as Owner for Azure Service Principal", "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add owner to service principal\" and event.outcome:(Success or success)", + "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add owner to service principal\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json index 30c2d3a9b4ab7..0977d8310cf6b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Persistence via Atom Init Script Modification", - "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:/Users/*/.atom/init.coffee and not process.name:(Atom or xpcproxy) and not user.name:root", + "query": "event.category:\"file\" and not event.type:\"deletion\" and\n file.path:/Users/*/.atom/init.coffee and not process.name:(Atom or xpcproxy) and not user.name:root\n", "references": [ "https://github.com/D00MFist/PersistentJXA/blob/master/AtomPersist.js", "https://flight-manual.atom.io/hacking-atom/sections/the-init-file/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json index e6bd7784006ee..6023854cd6641 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Privilege Escalation via Sudoers File Modification", - "query": "event.category:process and event.type:start and process.args:(echo and *NOPASSWD*ALL*)", + "query": "event.category:process and event.type:start and process.args:(echo and *NOPASSWD*ALL*)\n", "risk_score": 73, "rule_id": "76152ca1-71d0-4003-9e37-0983e12832da", "severity": "high", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json index c8ec829d3ae8a..32c47afd93019 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Execution with Explicit Credentials via Scripting", - "query": "event.category:process and event.type:(start or process_started) and process.name:\"security_authtrampoline\" and process.parent.name:(osascript or com.apple.automator.runner or sh or bash or dash or zsh or python* or perl* or php* or ruby or pwsh)", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:\"security_authtrampoline\" and\n process.parent.name:(osascript or com.apple.automator.runner or sh or bash or dash or zsh or python* or perl* or php* or ruby or pwsh)\n", "references": [ "https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf", "https://www.manpagez.com/man/8/security_authtrampoline/" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json index 0fb4285ea2a98..0c30cbb23b56b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Child Process of Adobe Acrobat Reader Update Service", - "query": "event.category:process and event.type:(start or process_started) and process.parent.name:com.adobe.ARMDC.SMJobBlessHelper and user.name:root and not process.executable: (/Library/PrivilegedHelperTools/com.adobe.ARMDC.SMJobBlessHelper or /usr/bin/codesign or /private/var/folders/zz/*/T/download/ARMDCHammer or /usr/sbin/pkgutil or /usr/bin/shasum or /usr/bin/perl* or /usr/sbin/spctl or /usr/sbin/installer)", + "query": "event.category:process and event.type:(start or process_started) and\n process.parent.name:com.adobe.ARMDC.SMJobBlessHelper and\n user.name:root and\n not process.executable: (/Library/PrivilegedHelperTools/com.adobe.ARMDC.SMJobBlessHelper or\n /usr/bin/codesign or\n /private/var/folders/zz/*/T/download/ARMDCHammer or\n /usr/sbin/pkgutil or\n /usr/bin/shasum or\n /usr/bin/perl* or\n /usr/sbin/spctl or\n /usr/sbin/installer)\n", "references": [ "https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json index 7000e119f8881..4b1b367a5ea35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Modification of Dynamic Linker Preload Shared Object", - "query": "event.category:file and not event.type:deletion and file.path:/etc/ld.so.preload", + "query": "event.category:file and not event.type:deletion and file.path:/etc/ld.so.preload\n", "references": [ "https://www.anomali.com/blog/rocke-evolves-its-arsenal-with-a-new-malware-family-written-in-golang" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json index 58c151d6de0c4..72c3bfc9a520c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Admin Group Account Addition", - "query": "event.category:process and event.type:(start or process_started) and process.name:(dscl or dseditgroup) and process.args:((\"/Groups/admin\" or admin) and (\"-a\" or \"-append\"))", + "query": "event.category:process and event.type:(start or process_started) and\n process.name:(dscl or dseditgroup) and process.args:((\"/Groups/admin\" or admin) and (\"-a\" or \"-append\"))\n", "references": [ "https://managingosx.wordpress.com/2010/01/14/add-a-user-to-the-admin-group-via-command-line-3-0/" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json index a85558df6f388..e454a387b3883 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious DLL Loaded for Persistence or Privilege Escalation", - "query": "library where dll.name :\n (\n \"wlbsctrl.dll\",\n \"wbemcomn.dll\",\n \"WptsExtensions.dll\",\n \"Tsmsisrv.dll\",\n \"TSVIPSrv.dll\",\n \"Msfte.dll\",\n \"wow64log.dll\",\n \"WindowsCoreDeviceInfo.dll\",\n \"Ualapi.dll\",\n \"wlanhlp.dll\",\n \"phoneinfo.dll\",\n \"EdgeGdi.dll\",\n \"cdpsgshims.dll\",\n \"windowsperformancerecordercontrol.dll\",\n \"diagtrack_win.dll\"\n ) and \nnot (dll.code_signature.subject_name : \"Microsoft Windows\" and dll.code_signature.status : \"trusted\")\n", + "query": "library where dll.name :\n (\n \"wlbsctrl.dll\",\n \"wbemcomn.dll\",\n \"WptsExtensions.dll\",\n \"Tsmsisrv.dll\",\n \"TSVIPSrv.dll\",\n \"Msfte.dll\",\n \"wow64log.dll\",\n \"WindowsCoreDeviceInfo.dll\",\n \"Ualapi.dll\",\n \"wlanhlp.dll\",\n \"phoneinfo.dll\",\n \"EdgeGdi.dll\",\n \"cdpsgshims.dll\",\n \"windowsperformancerecordercontrol.dll\",\n \"diagtrack_win.dll\"\n ) and \nnot (dll.code_signature.subject_name : (\"Microsoft Windows\", \"Microsoft Corporation\") and dll.code_signature.status : \"trusted\")\n", "references": [ "https://itm4n.github.io/windows-dll-hijacking-clarified/", "http://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html", @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json index f782e429dd29e..57f893168ba57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Privilege Escalation via Root Crontab File Modification", - "query": "event.category:file and not event.type:deletion and file.path:/private/var/at/tabs/root and not process.executable:/usr/bin/crontab", + "query": "event.category:file and not event.type:deletion and\n file.path:/private/var/at/tabs/root and not process.executable:/usr/bin/crontab\n", "references": [ "https://phoenhex.re/2017-06-09/pwn2own-diskarbitrationd-privesc", "https://www.exploit-db.com/exploits/42146" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json index 5ab6b41582030..33b5cd400fd33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS Root Login Without MFA", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and aws.cloudtrail.user_identity.type:Root and aws.cloudtrail.console_login.additional_eventdata.mfa_used:false and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:signin.amazonaws.com and event.action:ConsoleLogin and\n aws.cloudtrail.user_identity.type:Root and\n aws.cloudtrail.console_login.additional_eventdata.mfa_used:false and\n event.outcome:success\n", "references": [ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json index 67633985221e3..29113d078ae5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "max_signals": 33, "name": "Setuid / Setgid Bit Set via chmod", - "query": "event.category:process AND event.type:(start OR process_started) AND process.name:chmod AND process.args:(\"+s\" OR \"u+s\" OR /4[0-9]{3}/ OR g+s OR /2[0-9]{3}/) AND NOT process.args: ( /.*\\/Applications\\/VirtualBox.app\\/.+/ OR /\\/usr\\/local\\/lib\\/python.+/ OR /\\/var\\/folders\\/.+\\/FP.*nstallHelper/ OR /\\/Library\\/Filesystems\\/.+/ OR /\\/usr\\/lib\\/virtualbox\\/.+/ OR /\\/Library\\/Application.*/ OR \"/run/postgresql\" OR \"/var/crash\" OR \"/var/run/postgresql\" OR /\\/usr\\/bin\\/.+/ OR /\\/usr\\/local\\/share\\/.+/ OR /\\/Applications\\/.+/ OR /\\/usr\\/libexec\\/.+/ OR \"/var/metrics\" OR /\\/var\\/lib\\/dpkg\\/.+/ OR /\\/run\\/log\\/journal\\/.*/ OR \\/Users\\/*\\/.minikube\\/bin\\/docker-machine-driver-hyperkit ) AND NOT process.parent.executable: ( /\\/var\\/lib\\/docker\\/.+/ OR \"/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/XPCServices/package_script_service.xpc/Contents/MacOS/package_script_service\" OR \"/var/lib/dpkg/info/whoopsie.postinst\" )", + "query": "event.category:process AND event.type:(start OR process_started) AND\n process.name:chmod AND process.args:(\"+s\" OR \"u+s\" OR /4[0-9]{3}/ OR g+s OR /2[0-9]{3}/) AND\n NOT process.args:\n (\n /.*\\/Applications\\/VirtualBox.app\\/.+/ OR\n /\\/usr\\/local\\/lib\\/python.+/ OR\n /\\/var\\/folders\\/.+\\/FP.*nstallHelper/ OR\n /\\/Library\\/Filesystems\\/.+/ OR\n /\\/usr\\/lib\\/virtualbox\\/.+/ OR\n /\\/Library\\/Application.*/ OR\n \"/run/postgresql\" OR\n \"/var/crash\" OR\n \"/var/run/postgresql\" OR\n /\\/usr\\/bin\\/.+/ OR /\\/usr\\/local\\/share\\/.+/ OR\n /\\/Applications\\/.+/ OR /\\/usr\\/libexec\\/.+/ OR\n \"/var/metrics\" OR /\\/var\\/lib\\/dpkg\\/.+/ OR\n /\\/run\\/log\\/journal\\/.*/ OR\n \\/Users\\/*\\/.minikube\\/bin\\/docker-machine-driver-hyperkit\n ) AND\n NOT process.parent.executable:\n (\n /\\/var\\/lib\\/docker\\/.+/ OR\n \"/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/XPCServices/package_script_service.xpc/Contents/MacOS/package_script_service\" OR\n \"/var/lib/dpkg/info/whoopsie.postinst\"\n )\n", "risk_score": 21, "rule_id": "8a1b0278-0f9a-487d-96bd-d4833298e87a", "severity": "low", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json index 144cea40e6a4b..1c7a064131171 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Sudo Heap-Based Buffer Overflow Attempt", - "query": "event.category:process and event.type:start and process.name:(sudo or sudoedit) and process.args:(*\\\\ and (\"-i\" or \"-s\"))", + "query": "event.category:process and event.type:start and\n process.name:(sudo or sudoedit) and\n process.args:(*\\\\ and (\"-i\" or \"-s\"))\n", "references": [ "https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-3156", "https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit", 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 1b480e3d19650..963c16ae4dd61 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 @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Sudoers File Modification", - "query": "event.category:file and event.type:change and file.path:(/etc/sudoers* or /private/etc/sudoers*)", + "query": "event.category:file and event.type:change and file.path:(/etc/sudoers* or /private/etc/sudoers*)\n", "risk_score": 47, "rule_id": "931e25a5-0f5e-4ae0-ba0d-9e94eff7e3a4", "severity": "medium", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json index 8f01db818dfb9..e46e59feea6f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json @@ -16,7 +16,7 @@ "license": "Elastic License v2", "name": "AWS IAM Assume Role Policy Update", "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:UpdateAssumeRolePolicy and event.outcome:success", + "query": "event.dataset:aws.cloudtrail and event.provider:iam.amazonaws.com and event.action:UpdateAssumeRolePolicy and event.outcome:success\n", "references": [ "https://labs.bishopfox.com/tech-blog/5-privesc-attack-vectors-in-aws" ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json index 9a058b50683b6..f582eba053d64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "Threat Intel Filebeat Module Indicator Match", "note": "## Triage and Analysis\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threatintel.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threatintel.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threatintel.indicator.matched.type` - this identifies the indicator type that matched the local observation\n", - "query": "file.hash.*:* or file.pe.imphash:* or source.ip:* or destination.ip:* or url.full:* or registry.path:*", + "query": "file.hash.*:* or file.pe.imphash:* or source.ip:* or destination.ip:* or url.full:* or registry.path:*\n", "references": [ "https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-threatintel.html" ], diff --git a/x-pack/test/fleet_api_integration/apis/epm/delete.ts b/x-pack/test/fleet_api_integration/apis/epm/delete.ts index c79a29a858b46..5f90805e5879f 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/delete.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/delete.ts @@ -11,7 +11,7 @@ import { skipIfNoDockerRegistry } from '../../helpers'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); - const requiredPackage = 'system-0.12.6'; + const requiredPackage = 'system-0.13.3'; const installPackage = async (pkgkey: string) => { await supertest diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index d18ba9c55ca96..d1c6c3c3f6b1e 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -15,8 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - process.env.FLEET_PACKAGE_REGISTRY_DOCKER_IMAGE || - 'docker.elastic.co/package-registry/distribution:fc104ac437370d80518e24da6d0b84370edf0c0c'; + 'docker.elastic.co/package-registry/distribution@sha256:35cedaaa6adac547947321fa0c3b60a63eba153ba09524b9c1a21f1247a09bd2'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); From bd2215f587de0da0613bf1c5a71c71b8efbd868f Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Thu, 24 Jun 2021 16:46:19 -0600 Subject: [PATCH 15/40] [docs][migrations v2] Update SO migration docs to include removal of index write block when handling corrupt SOs. (#103014) --- .../setup/upgrade/upgrade-migrations.asciidoc | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index fdcd71791ad3a..947043b21ef50 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -55,22 +55,55 @@ This section highlights common causes of {kib} upgrade failures and how to preve There is a known issue in v7.12.0 for users who tried the fleet beta. Upgrade migrations fail because of a large number of documents in the `.kibana` index. This can cause Kibana to log errors like: -> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] -> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] + +[source,sh] +-------------------------------------------- +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] + +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] +-------------------------------------------- See https://github.com/elastic/kibana/issues/95321 for instructions to work around this issue. [float] ===== Corrupt saved objects -We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. +We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. + +Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. For example, given the following error message: -> Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. -The following steps must be followed to allow the upgrade migration to succeed. -Please be aware the Dashboard having ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` belonging to the space `marketing_space` will no more be available: -1. Delete the corrupt document with `DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275` -2. Restart {kib} +[source,sh] +-------------------------------------------- +Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. +-------------------------------------------- + +The following steps must be followed to delete the document that is causing the migration to fail: + +. Remove the write block which the migration system has placed on the previous index: ++ +[source,sh] +-------------------------------------------- +PUT .kibana_7.12.1_001/_settings +{ + "index": { + "blocks.write": false + } +} +-------------------------------------------- + +. Delete the corrupt document: ++ +[source,sh] +-------------------------------------------- +DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275 +-------------------------------------------- + +. Restart {kib}. + +In this example, the Dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **will no longer be available**. + +Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying. [float] ===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings From 803d0fa57ba0cb955b846b52c2d8489cb101312c Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 24 Jun 2021 16:02:48 -0700 Subject: [PATCH 16/40] [Enterprise Search] Final KibanaPageTemplate cleanup (#103355) * [AS] Delete AppSearchNav and EngineNav * [WS] Delete WorkplaceSearchNav * [Shared] Delete custom Layout & SideNav components --- .../components/engine/engine_nav.test.tsx | 184 +------------- .../components/engine/engine_nav.tsx | 235 +----------------- .../app_search/components/engine/index.ts | 1 - .../applications/app_search/index.test.tsx | 55 +--- .../public/applications/app_search/index.tsx | 39 +-- .../applications/shared/layout/index.ts | 4 - .../applications/shared/layout/layout.scss | 96 ------- .../shared/layout/layout.test.tsx | 77 ------ .../applications/shared/layout/layout.tsx | 82 ------ .../applications/shared/layout/side_nav.scss | 87 ------- .../shared/layout/side_nav.test.tsx | 166 ------------- .../applications/shared/layout/side_nav.tsx | 130 ---------- .../shared/layout/side_nav_bg.svg | 25 -- .../components/layout/index.ts | 2 +- .../components/layout/nav.test.tsx | 21 +- .../components/layout/nav.tsx | 41 +-- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 18 files changed, 18 insertions(+), 1233 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav_bg.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx index 015fb997c29ed..e088678a13562 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx @@ -10,7 +10,6 @@ import { mockUseRouteMatch } from '../../../__mocks__/react_router'; import { mockEngineValues } from '../../__mocks__'; jest.mock('../../../shared/layout', () => ({ - ...jest.requireActual('../../../shared/layout'), // TODO: Remove once side nav components are gone generateNavLink: jest.fn(({ to }) => ({ href: to })), })); @@ -20,9 +19,7 @@ import { shallow } from 'enzyme'; import { EuiBadge, EuiIcon } from '@elastic/eui'; -import { rerender } from '../../../test_helpers'; - -import { useEngineNav, EngineNav } from './engine_nav'; +import { useEngineNav } from './engine_nav'; describe('useEngineNav', () => { const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; @@ -321,182 +318,3 @@ describe('useEngineNav', () => { }); }); }); - -describe('EngineNav', () => { - const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; - - beforeEach(() => { - setMockValues(values); - }); - - it('does not render if async data is still loading', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('does not render without an engine name', () => { - setMockValues({ ...values, engineName: '' }); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('renders an engine label and badges', () => { - setMockValues({ ...values, isSampleEngine: false, isMetaEngine: false }); - const wrapper = shallow(); - const label = wrapper.find('[data-test-subj="EngineLabel"]').find('.eui-textTruncate'); - - expect(label.text()).toEqual('SOME-ENGINE'); - expect(wrapper.find(EuiBadge)).toHaveLength(0); - - setMockValues({ ...values, isSampleEngine: true }); - rerender(wrapper); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('SAMPLE ENGINE'); - - setMockValues({ ...values, isMetaEngine: true }); - rerender(wrapper); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('META ENGINE'); - }); - - it('renders a default engine overview link', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineOverviewLink"]')).toHaveLength(1); - }); - - it('renders an analytics link', () => { - setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineAnalyticsLink"]')).toHaveLength(1); - }); - - it('renders a documents link', () => { - setMockValues({ ...values, myRole: { canViewEngineDocuments: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineDocumentsLink"]')).toHaveLength(1); - }); - - it('renders a schema link', () => { - setMockValues({ ...values, myRole: { canViewEngineSchema: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineSchemaLink"]')).toHaveLength(1); - }); - - describe('schema nav icons', () => { - const myRole = { canViewEngineSchema: true }; - - it('renders schema errors alert icon', () => { - setMockValues({ ...values, myRole, hasSchemaErrors: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineNavSchemaErrors"]')).toHaveLength(1); - }); - - it('renders unconfirmed schema fields info icon', () => { - setMockValues({ ...values, myRole, hasUnconfirmedSchemaFields: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineNavSchemaUnconfirmedFields"]')).toHaveLength(1); - }); - - it('renders schema conflicts alert icon', () => { - setMockValues({ ...values, myRole, hasSchemaConflicts: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineNavSchemaConflicts"]')).toHaveLength(1); - }); - }); - - describe('crawler link', () => { - const myRole = { canViewEngineCrawler: true }; - - it('renders', () => { - setMockValues({ ...values, myRole }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(1); - }); - - it('does not render for meta engines', () => { - setMockValues({ ...values, myRole, isMetaEngine: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0); - }); - }); - - describe('meta engine source engines link', () => { - const myRole = { canViewMetaEngineSourceEngines: true }; - - it('renders', () => { - setMockValues({ ...values, myRole, isMetaEngine: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(1); - }); - - it('does not render for non meta engines', () => { - setMockValues({ ...values, myRole, isMetaEngine: false }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(0); - }); - }); - - it('renders a relevance tuning link', () => { - setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineRelevanceTuningLink"]')).toHaveLength(1); - }); - - describe('relevance tuning nav icons', () => { - const myRole = { canManageEngineRelevanceTuning: true }; - - it('renders unconfirmed schema fields info icon', () => { - const engine = { unsearchedUnconfirmedFields: true }; - setMockValues({ ...values, myRole, engine }); - const wrapper = shallow(); - expect( - wrapper.find('[data-test-subj="EngineNavRelevanceTuningUnsearchedFields"]') - ).toHaveLength(1); - }); - - it('renders schema conflicts alert icon', () => { - const engine = { invalidBoosts: true }; - setMockValues({ ...values, myRole, engine }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineNavRelevanceTuningInvalidBoosts"]')).toHaveLength( - 1 - ); - }); - - it('can render multiple icons', () => { - const engine = { invalidBoosts: true, unsearchedUnconfirmedFields: true }; - setMockValues({ ...values, myRole, engine }); - const wrapper = shallow(); - expect(wrapper.find(EuiIcon)).toHaveLength(2); - }); - }); - - it('renders a synonyms link', () => { - setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineSynonymsLink"]')).toHaveLength(1); - }); - - it('renders a curations link', () => { - setMockValues({ ...values, myRole: { canManageEngineCurations: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCurationsLink"]')).toHaveLength(1); - }); - - it('renders a results settings link', () => { - setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineResultSettingsLink"]')).toHaveLength(1); - }); - - it('renders a Search UI link', () => { - setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineSearchUILink"]')).toHaveLength(1); - }); - - it('renders an API logs link', () => { - setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineAPILogsLink"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 76e751cf4da5f..70f2d04a5123d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -10,17 +10,10 @@ import { useRouteMatch } from 'react-router-dom'; import { useValues } from 'kea'; -import { - EuiSideNavItemType, - EuiText, - EuiBadge, - EuiIcon, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; +import { EuiSideNavItemType, EuiText, EuiBadge, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { generateNavLink, SideNavLink, SideNavItem } from '../../../shared/layout'; +import { generateNavLink } from '../../../shared/layout'; import { AppLogic } from '../../app_logic'; import { ENGINE_PATH, @@ -49,8 +42,6 @@ import { SCHEMA_TITLE } from '../schema'; import { SEARCH_UI_TITLE } from '../search_ui'; import { SYNONYMS_TITLE } from '../synonyms'; -import { EngineDetails } from './types'; - import { EngineLogic, generateEnginePath } from './'; import './engine_nav.scss'; @@ -198,7 +189,10 @@ export const useEngineNav = () => { navItems.push({ id: 'crawler', name: CRAWLER_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_CRAWLER_PATH) }), + ...generateNavLink({ + to: generateEnginePath(ENGINE_CRAWLER_PATH), + shouldShowActiveForSubroutes: true, + }), 'data-test-subj': 'EngineCrawlerLink', }); } @@ -301,220 +295,3 @@ export const useEngineNav = () => { return navItems; }; - -// TODO: Delete the below once page template migration is complete - -export const EngineNav: React.FC = () => { - const { - myRole: { - canViewEngineAnalytics, - canViewEngineDocuments, - canViewEngineSchema, - canViewEngineCrawler, - canViewMetaEngineSourceEngines, - canManageEngineSynonyms, - canManageEngineCurations, - canManageEngineRelevanceTuning, - canManageEngineResultSettings, - canManageEngineSearchUi, - canViewEngineApiLogs, - }, - } = useValues(AppLogic); - - const { - engineName, - dataLoading, - isSampleEngine, - isMetaEngine, - hasSchemaErrors, - hasSchemaConflicts, - hasUnconfirmedSchemaFields, - engine, - } = useValues(EngineLogic); - - if (dataLoading) return null; - if (!engineName) return null; - - const { invalidBoosts, unsearchedUnconfirmedFields } = engine as Required; - - return ( - <> - - -
{engineName.toUpperCase()}
- {isSampleEngine && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge', { - defaultMessage: 'SAMPLE ENGINE', - })} - - )} - {isMetaEngine && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.metaEngineBadge', { - defaultMessage: 'META ENGINE', - })} - - )} -
-
- - {OVERVIEW_TITLE} - - {canViewEngineAnalytics && ( - - {ANALYTICS_TITLE} - - )} - {canViewEngineDocuments && ( - - {DOCUMENTS_TITLE} - - )} - {canViewEngineSchema && ( - - - {SCHEMA_TITLE} - - {hasSchemaErrors && ( - - )} - {hasUnconfirmedSchemaFields && ( - - )} - {hasSchemaConflicts && ( - - )} - - - - )} - {canViewEngineCrawler && !isMetaEngine && ( - - {CRAWLER_TITLE} - - )} - {canViewMetaEngineSourceEngines && isMetaEngine && ( - - {ENGINES_TITLE} - - )} - {canManageEngineRelevanceTuning && ( - - - {RELEVANCE_TUNING_TITLE} - - {invalidBoosts && ( - - )} - {unsearchedUnconfirmedFields && ( - - )} - - - - )} - {canManageEngineSynonyms && ( - - {SYNONYMS_TITLE} - - )} - {canManageEngineCurations && ( - - {CURATIONS_TITLE} - - )} - {canManageEngineResultSettings && ( - - {RESULT_SETTINGS_TITLE} - - )} - {canManageEngineSearchUi && ( - - {SEARCH_UI_TITLE} - - )} - {canViewEngineApiLogs && ( - - {API_LOGS_TITLE} - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts index 2a5b3351f41f7..86b4ff21e62d3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts @@ -6,6 +6,5 @@ */ export { EngineRouter } from './engine_router'; -export { EngineNav } from './engine_nav'; export { EngineLogic } from './engine_logic'; export { generateEnginePath, getEngineBreadcrumbs } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 46596cc5d6765..6647b4032e4bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -7,7 +7,6 @@ import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__'; import { setMockValues } from '../__mocks__/kea_logic'; -import { mockUseRouteMatch } from '../__mocks__/react_router'; import '../__mocks__/shallow_useeffect.mock'; import '../__mocks__/enterprise_search_url.mock'; @@ -17,15 +16,13 @@ import { Redirect } from 'react-router-dom'; import { shallow, ShallowWrapper } from 'enzyme'; -import { SideNav, SideNavLink } from '../shared/layout'; - import { rerender } from '../test_helpers'; jest.mock('./app_logic', () => ({ AppLogic: jest.fn() })); import { AppLogic } from './app_logic'; import { Credentials } from './components/credentials'; -import { EngineRouter, EngineNav } from './components/engine'; +import { EngineRouter } from './components/engine'; import { EngineCreation } from './components/engine_creation'; import { EnginesOverview } from './components/engines'; import { ErrorConnecting } from './components/error_connecting'; @@ -35,7 +32,7 @@ import { RoleMappings } from './components/role_mappings'; import { Settings } from './components/settings'; import { SetupGuide } from './components/setup_guide'; -import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './'; +import { AppSearch, AppSearchUnconfigured, AppSearchConfigured } from './'; describe('AppSearch', () => { it('always renders the Setup Guide', () => { @@ -142,51 +139,3 @@ describe('AppSearchConfigured', () => { }); }); }); - -describe('AppSearchNav', () => { - it('renders with the Engines link', () => { - const wrapper = shallow(); - - expect(wrapper.find(SideNav)).toHaveLength(1); - expect(wrapper.find(SideNavLink).prop('to')).toEqual('/engines'); - }); - - describe('engine subnavigation', () => { - const getEnginesLink = (wrapper: ShallowWrapper) => wrapper.find(SideNavLink).dive(); - - it('does not render the engine subnav on top-level routes', () => { - mockUseRouteMatch.mockReturnValueOnce(false); - const wrapper = shallow(); - - expect(getEnginesLink(wrapper).find(EngineNav)).toHaveLength(0); - }); - - it('renders the engine subnav if currently on an engine route', () => { - mockUseRouteMatch.mockReturnValueOnce(true); - const wrapper = shallow(); - - expect(getEnginesLink(wrapper).find(EngineNav)).toHaveLength(1); - }); - }); - - it('renders the Settings link', () => { - setMockValues({ myRole: { canViewSettings: true } }); - const wrapper = shallow(); - - expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/settings'); - }); - - it('renders the Credentials link', () => { - setMockValues({ myRole: { canViewAccountCredentials: true } }); - const wrapper = shallow(); - - expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/credentials'); - }); - - it('renders the Role Mappings link', () => { - setMockValues({ myRole: { canViewRoleMappings: true } }); - const wrapper = shallow(); - - expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/users_and_roles'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 6d049b2015487..11f706bff028f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -6,30 +6,26 @@ */ import React, { useEffect } from 'react'; -import { Route, Redirect, Switch, useRouteMatch } from 'react-router-dom'; +import { Route, Redirect, Switch } from 'react-router-dom'; import { useValues } from 'kea'; -import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; -import { SideNav, SideNavLink } from '../shared/layout'; - -import { ROLE_MAPPINGS_TITLE } from '../shared/role_mapping/constants'; import { AppLogic } from './app_logic'; -import { Credentials, CREDENTIALS_TITLE } from './components/credentials'; -import { EngineNav, EngineRouter } from './components/engine'; +import { Credentials } from './components/credentials'; +import { EngineRouter } from './components/engine'; import { EngineCreation } from './components/engine_creation'; -import { EnginesOverview, ENGINES_TITLE } from './components/engines'; +import { EnginesOverview } from './components/engines'; import { ErrorConnecting } from './components/error_connecting'; import { KibanaHeaderActions } from './components/layout'; import { Library } from './components/library'; import { MetaEngineCreation } from './components/meta_engine_creation'; import { NotFound } from './components/not_found'; import { RoleMappings } from './components/role_mappings'; -import { Settings, SETTINGS_TITLE } from './components/settings'; +import { Settings } from './components/settings'; import { SetupGuide } from './components/setup_guide'; import { ENGINE_CREATION_PATH, @@ -137,28 +133,3 @@ export const AppSearchConfigured: React.FC> = (props) = ); }; - -export const AppSearchNav: React.FC = () => { - const { - myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, - } = useValues(AppLogic); - - const isEngineRoute = !!useRouteMatch(ENGINE_PATH); - - return ( - - : null} isRoot> - {ENGINES_TITLE} - - {canViewSettings && {SETTINGS_TITLE}} - {canViewAccountCredentials && ( - {CREDENTIALS_TITLE} - )} - {canViewRoleMappings && ( - - {ROLE_MAPPINGS_TITLE} - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts index 856d483e174a6..41f8869ad5f61 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts @@ -7,7 +7,3 @@ export { EnterpriseSearchPageTemplate, PageTemplateProps } from './page_template'; export { generateNavLink } from './nav_link_helpers'; - -// TODO: Delete these once KibanaPageTemplate migration is done -export { Layout } from './layout'; -export { SideNav, SideNavLink, SideNavItem } from './side_nav'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.scss b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.scss deleted file mode 100644 index 88799406070cd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.scss +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -.enterpriseSearchLayout { - $sideBarWidth: $euiSize * 15; - $sideBarMobileHeight: $euiSize * 4.75; - $consoleHeaderHeight: 98px; // NOTE: Keep an eye on this for changes - $pageHeight: calc(100vh - #{$consoleHeaderHeight}); - - display: block; - background-color: $euiColorEmptyShade; - min-height: $pageHeight; - position: relative; - left: $sideBarWidth; - width: calc(100% - #{$sideBarWidth}); - padding: 0; - - @include euiBreakpoint('xs', 's', 'm') { - left: auto; - width: 100%; - } - - &__sideBarToggle { - display: none; - - @include euiBreakpoint('xs', 's', 'm') { - display: block; - - position: absolute; - right: $euiSize; - top: $sideBarMobileHeight / 2; - transform: translateY(-50%) scale(.9); - - .euiButton { - min-width: 0; - } - } - } - - &__sideBar { - z-index: $euiZNavigation; - position: fixed; - margin-left: -1 * $sideBarWidth; - margin-right: 0; - overflow-y: auto; - overflow-x: hidden; - - height: $pageHeight; - width: $sideBarWidth; - - background-color: $euiColorLightestShade; - box-shadow: inset (-1 * $euiSizeXS) 0 $euiSizeS (-1 * $euiSizeXS) rgba($euiShadowColor, .25); - - @include euiBreakpoint('xs', 's', 'm') { - position: relative; - width: 100%; - height: $sideBarMobileHeight; - margin-left: 0; - overflow-y: hidden; - - border-bottom: $euiBorderThin; - box-shadow: none; - - &--isOpen { - height: auto; - overflow-y: auto; - } - } - } - - &__body { - padding: $euiSizeXXL; - - @include euiBreakpoint('m') { - padding: $euiSizeL; - } - @include euiBreakpoint('xs', 's') { - padding: $euiSize; - } - } - - &__readOnlyMode { - margin: -$euiSizeM 0 $euiSizeL; - - @include euiBreakpoint('m') { - margin: 0 0 $euiSizeL; - } - @include euiBreakpoint('xs', 's') { - margin: 0; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx deleted file mode 100644 index 28092f75cdede..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiPageSideBar, EuiButton, EuiPageBody, EuiCallOut } from '@elastic/eui'; - -import { Layout, INavContext } from './layout'; - -describe('Layout', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find('.enterpriseSearchLayout')).toHaveLength(1); - expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toBeFalsy(); - }); - - it('passes the restrictWidth prop', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toEqual(true); - }); - - it('renders navigation', () => { - const wrapper = shallow(Hello World} />); - - expect(wrapper.find('.enterpriseSearchLayout__sideBar')).toHaveLength(1); - expect(wrapper.find('.nav-test')).toHaveLength(1); - }); - - it('renders navigation toggle state', () => { - const wrapper = shallow(Hello World} />); - expect(wrapper.find(EuiPageSideBar).prop('className')).not.toContain('--isOpen'); - expect(wrapper.find(EuiButton).prop('iconType')).toEqual('arrowRight'); - - const toggle = wrapper.find('[data-test-subj="enterpriseSearchNavToggle"]'); - toggle.simulate('click'); - - expect(wrapper.find(EuiPageSideBar).prop('className')).toContain('--isOpen'); - expect(wrapper.find(EuiButton).prop('iconType')).toEqual('arrowDown'); - }); - - it('passes down NavContext to navigation links', () => { - const wrapper = shallow(} />); - - const toggle = wrapper.find('[data-test-subj="enterpriseSearchNavToggle"]'); - toggle.simulate('click'); - expect(wrapper.find(EuiPageSideBar).prop('className')).toContain('--isOpen'); - - const context = (wrapper.find('ContextProvider').prop('value') as unknown) as INavContext; - context.closeNavigation(); - expect(wrapper.find(EuiPageSideBar).prop('className')).not.toContain('--isOpen'); - }); - - it('renders a read-only mode callout', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - }); - - it('renders children', () => { - const wrapper = shallow( - -
Test
-
- ); - - expect(wrapper.find('.enterpriseSearchLayout__body')).toHaveLength(1); - expect(wrapper.find('.testing')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx deleted file mode 100644 index 9cf5fccddbd5b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; - -import classNames from 'classnames'; - -import { EuiPage, EuiPageSideBar, EuiPageBody, EuiButton, EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import './layout.scss'; - -interface LayoutProps { - navigation: React.ReactNode; - restrictWidth?: boolean; - readOnlyMode?: boolean; -} - -export interface INavContext { - closeNavigation(): void; -} -export const NavContext = React.createContext({}); - -export const Layout: React.FC = ({ - children, - navigation, - restrictWidth, - readOnlyMode, -}) => { - const [isNavOpen, setIsNavOpen] = useState(false); - const toggleNavigation = () => setIsNavOpen(!isNavOpen); - const closeNavigation = () => setIsNavOpen(false); - - const navClasses = classNames('enterpriseSearchLayout__sideBar', { - 'enterpriseSearchLayout__sideBar--isOpen': isNavOpen, // eslint-disable-line @typescript-eslint/naming-convention - }); - - return ( - - -
- - {i18n.translate('xpack.enterpriseSearch.nav.menu', { - defaultMessage: 'Menu', - })} - -
- {navigation} -
- - {readOnlyMode && ( - - )} - {children} - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.scss b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.scss deleted file mode 100644 index f6a1ebed7ba93..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.scss +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -$euiSizeML: $euiSize * 1.25; // 20px - between medium and large ¯\_(ツ)_/¯ - -.enterpriseSearchProduct { - display: flex; - align-items: center; - padding: $euiSizeML; - - background-image: url('./side_nav_bg.svg'); - background-repeat: no-repeat; - - @include euiBreakpoint('xs', 's', 'm') { - padding: $euiSize $euiSizeML; - } - - &__icon { - display: flex; - align-items: center; - justify-content: center; - - width: $euiSizeXXL; - height: $euiSizeXXL; - margin-right: $euiSizeS; - - background-color: $euiColorEmptyShade; - border-radius: 50%; - @include euiSlightShadow(); - - .euiIcon { - width: $euiSizeML; - height: $euiSizeML; - } - } - - &__title { - .euiText { - font-weight: $euiFontWeightMedium; - } - } -} - -.enterpriseSearchNavLinks { - &__item { - display: block; - padding: $euiSizeM $euiSizeML; - font-size: $euiFontSizeS; - font-weight: $euiFontWeightMedium; - line-height: $euiFontSizeM; - - $activeBgColor: rgba($euiColorFullShade, .05); - - &--isActive { - background-color: $activeBgColor; - } - - &.euiLink { - color: $euiTextColor; - font-weight: $euiFontWeightMedium; - - &:hover { - color: $euiTextColor; - } - - &:focus { - outline: solid 0 $activeBgColor; - background-color: $activeBgColor; - } - } - } - - &__subNav { - padding-left: $euiSizeML; - - // Extends the click area of links more to the left, so that second tiers - // of subnavigation links still have the same hitbox as first tier links - .enterpriseSearchNavLinks__item { - margin-left: -$euiSizeML; - padding-left: $euiSizeXXL; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx deleted file mode 100644 index 244037d6e1382..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mockLocation } from '../../__mocks__/react_router'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiLink } from '@elastic/eui'; - -import { ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN } from '../../../../common/constants'; -import { EuiLinkTo } from '../react_router_helpers'; - -import { SideNav, SideNavLink, SideNavItem } from './'; - -describe('SideNav', () => { - it('renders link children', () => { - const wrapper = shallow( - -
Hello World
-
- ); - - expect(wrapper.type()).toEqual('nav'); - expect(wrapper.find('.enterpriseSearchNavLinks')).toHaveLength(1); - expect(wrapper.find('.testing')).toHaveLength(1); - }); - - it('renders a custom product', () => { - const wrapper = shallow(); - - expect(wrapper.find('h3').text()).toEqual('App Search'); - expect(wrapper.find('.enterpriseSearchProduct--appSearch')).toHaveLength(1); - }); -}); - -describe('SideNavLink', () => { - it('renders', () => { - const wrapper = shallow(Link); - - expect(wrapper.type()).toEqual('li'); - expect(wrapper.find(EuiLinkTo)).toHaveLength(1); - expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1); - }); - - it('renders an external link if isExternal is true', () => { - const wrapper = shallow( - - Link - - ); - const externalLink = wrapper.find(EuiLink); - - expect(externalLink).toHaveLength(1); - expect(externalLink.prop('href')).toEqual('http://website.com'); - expect(externalLink.prop('target')).toEqual('_blank'); - }); - - it('sets an active class if the current path matches the nav link', () => { - mockLocation.pathname = '/test/'; - - const wrapper = shallow(Link); - - expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1); - }); - - it('sets an active class if the current path is / and the link isRoot', () => { - mockLocation.pathname = '/'; - - const wrapper = shallow( - - Link - - ); - - expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1); - }); - - it('passes down custom classes and props', () => { - const wrapper = shallow( - - Link - - ); - - expect(wrapper.find('.testing')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="testing"]')).toHaveLength(1); - }); - - it('renders nested subnavigation', () => { - const subNav = ( - - Another link! - - ); - const wrapper = shallow( - - Link - - ); - - expect(wrapper.find('.enterpriseSearchNavLinks__subNav')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="subNav"]')).toHaveLength(1); - }); - - describe('shouldShowActiveForSubroutes', () => { - it("won't set an active class when route is a subroute of 'to'", () => { - mockLocation.pathname = '/documents/1234'; - - const wrapper = shallow( - - Link - - ); - - expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(0); - }); - - it('sets an active class if the current path is a subRoute of "to", and shouldShowActiveForSubroutes is true', () => { - mockLocation.pathname = '/documents/1234'; - - const wrapper = shallow( - - Link - - ); - - expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1); - }); - }); -}); - -describe('SideNavItem', () => { - it('renders', () => { - const wrapper = shallow(Test); - - expect(wrapper.type()).toEqual('li'); - expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1); - }); - - it('renders children', () => { - const wrapper = shallow( - - World - - ); - - expect(wrapper.find('[data-test-subj="hello"]').text()).toEqual('World'); - }); - - it('passes down custom classes and props', () => { - const wrapper = shallow( - - Test - - ); - - expect(wrapper.find('.testing')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="testing"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx deleted file mode 100644 index 58a5c7bbb229f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useContext } from 'react'; -import { useLocation } from 'react-router-dom'; - -import classNames from 'classnames'; - -import { EuiIcon, EuiTitle, EuiText, EuiLink } from '@elastic/eui'; // TODO: Remove EuiLink after full Kibana transition -import { i18n } from '@kbn/i18n'; - -import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../common/constants'; -import { stripTrailingSlash } from '../../../../common/strip_slashes'; -import { EuiLinkTo } from '../react_router_helpers'; - -import { NavContext, INavContext } from './layout'; - -import './side_nav.scss'; - -/** - * Side navigation - product & icon + links wrapper - */ - -interface SideNavProps { - // Expects product plugin constants (@see common/constants.ts) - product: { - NAME: string; - ID: string; - }; -} - -export const SideNav: React.FC = ({ product, children }) => { - return ( - - ); -}; - -/** - * Side navigation link item - */ - -interface SideNavLinkProps { - to: string; - shouldShowActiveForSubroutes?: boolean; - isExternal?: boolean; - className?: string; - isRoot?: boolean; - subNav?: React.ReactNode; -} - -export const SideNavLink: React.FC = ({ - to, - shouldShowActiveForSubroutes = false, - isExternal, - children, - className, - isRoot, - subNav, - ...rest -}) => { - const { closeNavigation } = useContext(NavContext) as INavContext; - - const { pathname } = useLocation(); - const currentPath = stripTrailingSlash(pathname); - const isActive = - currentPath === to || - (shouldShowActiveForSubroutes && currentPath.startsWith(to)) || - (isRoot && currentPath === ''); - - const classes = classNames('enterpriseSearchNavLinks__item', className, { - 'enterpriseSearchNavLinks__item--isActive': !isExternal && isActive, // eslint-disable-line @typescript-eslint/naming-convention - }); - - return ( -
  • - {isExternal ? ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - - {children} - - ) : ( - - {children} - - )} - {subNav &&
      {subNav}
    } -
  • - ); -}; - -/** - * Side navigation non-link item - */ - -interface SideNavItemProps { - className?: string; -} - -export const SideNavItem: React.FC = ({ children, className, ...rest }) => { - const classes = classNames('enterpriseSearchNavLinks__item', className); - return ( -
  • - {children} -
  • - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav_bg.svg b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav_bg.svg deleted file mode 100644 index a19227ab7b7eb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav_bg.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts index 8cdc133681762..0cc39f66e3d20 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts @@ -6,7 +6,7 @@ */ export { WorkplaceSearchPageTemplate } from './page_template'; -export { useWorkplaceSearchNav, WorkplaceSearchNav } from './nav'; +export { useWorkplaceSearchNav } from './nav'; export { WorkplaceSearchHeaderActions } from './kibana_header_actions'; export { AccountHeader } from './account_header'; export { PersonalDashboardLayout } from './personal_dashboard_layout'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx index 04576e981e104..65a6a798b032a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx @@ -6,7 +6,6 @@ */ jest.mock('../../../shared/layout', () => ({ - ...jest.requireActual('../../../shared/layout'), generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })), })); jest.mock('../../views/content_sources/components/source_sub_nav', () => ({ @@ -19,13 +18,7 @@ jest.mock('../../views/settings/components/settings_sub_nav', () => ({ useSettingsSubNav: () => [], })); -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SideNav, SideNavLink } from '../../../shared/layout'; - -import { useWorkplaceSearchNav, WorkplaceSearchNav } from './'; +import { useWorkplaceSearchNav } from './'; describe('useWorkplaceSearchNav', () => { it('returns an array of top-level Workplace Search nav items', () => { @@ -72,15 +65,3 @@ describe('useWorkplaceSearchNav', () => { ]); }); }); - -// TODO: Delete below once fully migrated to KibanaPageTemplate - -describe('WorkplaceSearchNav', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SideNav)).toHaveLength(1); - expect(wrapper.find(SideNavLink).first().prop('to')).toEqual('/'); - expect(wrapper.find(SideNavLink)).toHaveLength(6); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index c8d821dcdae2e..7dc005a56bf10 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -5,12 +5,9 @@ * 2.0. */ -import React from 'react'; +import { EuiSideNavItemType } from '@elastic/eui'; -import { EuiSideNavItemType, EuiSpacer } from '@elastic/eui'; - -import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { generateNavLink, SideNav, SideNavLink } from '../../../shared/layout'; +import { generateNavLink } from '../../../shared/layout'; import { NAV } from '../../constants'; import { SOURCES_PATH, @@ -68,37 +65,3 @@ export const useWorkplaceSearchNav = () => { // to cause all our navItems to properly render as nav links. return [{ id: '', name: '', items: navItems }]; }; - -// TODO: Delete below once fully migrated to KibanaPageTemplate - -interface Props { - sourcesSubNav?: React.ReactNode; - groupsSubNav?: React.ReactNode; - settingsSubNav?: React.ReactNode; -} - -export const WorkplaceSearchNav: React.FC = ({ - sourcesSubNav, - groupsSubNav, - settingsSubNav, -}) => ( - - - {NAV.OVERVIEW} - - - {NAV.SOURCES} - - - {NAV.GROUPS} - - - {NAV.ROLE_MAPPINGS} - - {NAV.SECURITY} - - {NAV.SETTINGS} - - - -); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f86e020a0407..aab80fe308861 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7884,9 +7884,6 @@ "xpack.enterpriseSearch.featureCatalogueDescription2": "ユーザーを関連するデータにつなげます。", "xpack.enterpriseSearch.featureCatalogueDescription3": "チームの内容を統合します。", "xpack.enterpriseSearch.hiddenText": "非表示のテキスト", - "xpack.enterpriseSearch.nav.hierarchy": "セカンダリ", - "xpack.enterpriseSearch.nav.menu": "メニュー", - "xpack.enterpriseSearch.nav.toggleMenu": "セカンダリナビゲーションを切り替える", "xpack.enterpriseSearch.navTitle": "概要", "xpack.enterpriseSearch.notFound.action1": "ダッシュボードに戻す", "xpack.enterpriseSearch.notFound.action2": "サポートに問い合わせる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index acad67a0b1c7a..d4f263b58b8ab 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7952,9 +7952,6 @@ "xpack.enterpriseSearch.featureCatalogueDescription2": "将您的用户连接到相关数据。", "xpack.enterpriseSearch.featureCatalogueDescription3": "统一您的团队内容。", "xpack.enterpriseSearch.hiddenText": "隐藏文本", - "xpack.enterpriseSearch.nav.hierarchy": "次级", - "xpack.enterpriseSearch.nav.menu": "菜单", - "xpack.enterpriseSearch.nav.toggleMenu": "切换次级导航", "xpack.enterpriseSearch.navTitle": "概览", "xpack.enterpriseSearch.notFound.action1": "返回到您的仪表板", "xpack.enterpriseSearch.notFound.action2": "联系支持人员", From 205684540857cee84be923404c7e7b82939e002e Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Thu, 24 Jun 2021 18:05:11 -0500 Subject: [PATCH 17/40] [canvas] Reduce bundle size by combining SCSS imports (#102822) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../canvas/public/components/var_config/delete_var.tsx | 2 -- .../canvas/public/components/var_config/edit_var.tsx | 3 --- .../canvas/public/components/var_config/var_config.tsx | 2 -- x-pack/plugins/canvas/public/style/index.scss | 7 +++++++ x-pack/plugins/canvas/public/transitions/fade/index.ts | 2 -- x-pack/plugins/canvas/public/transitions/rotate/index.ts | 2 -- x-pack/plugins/canvas/public/transitions/slide/index.ts | 2 -- x-pack/plugins/canvas/public/transitions/zoom/index.ts | 2 -- 8 files changed, 7 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx index f6ba2d7e28825..1aea08a96784d 100644 --- a/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx +++ b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx @@ -39,8 +39,6 @@ const strings = { }), }; -import './var_panel.scss'; - interface Props { selectedVar: CanvasVariable; onDelete: (v: CanvasVariable) => void; diff --git a/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx index 35f9e67745aec..5501aa9aab637 100644 --- a/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx +++ b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx @@ -76,9 +76,6 @@ const strings = { }), }; -import './edit_var.scss'; -import './var_panel.scss'; - interface Props { selectedVar: CanvasVariable | null; variables: CanvasVariable[]; diff --git a/x-pack/plugins/canvas/public/components/var_config/var_config.tsx b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx index dc8898e2132e7..25c77ab7704bf 100644 --- a/x-pack/plugins/canvas/public/components/var_config/var_config.tsx +++ b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx @@ -25,8 +25,6 @@ import { CanvasVariable } from '../../../types'; import { EditVar } from './edit_var'; import { DeleteVar } from './delete_var'; -import './var_config.scss'; - enum PanelMode { List, Edit, diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index d9592d5c0be5f..e866eada1f85f 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -43,6 +43,13 @@ @import '../components/workpad_page/workpad_page'; @import '../components/workpad_page/workpad_interactive_page/workpad_interactive_page'; @import '../components/workpad_page/workpad_static_page/workpad_static_page'; +@import '../components/var_config/edit_var'; +@import '../components/var_config/var_config'; + +@import '../transitions/fade/fade'; +@import '../transitions/rotate/rotate'; +@import '../transitions/slide/slide'; +@import '../transitions/zoom/zoom'; @import '../../canvas_plugin_src/renderers/filters/advanced_filter/component/advanced_filter.scss'; @import '../../canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.scss'; diff --git a/x-pack/plugins/canvas/public/transitions/fade/index.ts b/x-pack/plugins/canvas/public/transitions/fade/index.ts index c8fcc574b1872..7ce717a83eeb0 100644 --- a/x-pack/plugins/canvas/public/transitions/fade/index.ts +++ b/x-pack/plugins/canvas/public/transitions/fade/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -import './fade.scss'; - import { TransitionStrings } from '../../../i18n'; const { fade: strings } = TransitionStrings; diff --git a/x-pack/plugins/canvas/public/transitions/rotate/index.ts b/x-pack/plugins/canvas/public/transitions/rotate/index.ts index 217fd26680959..959e1ae248f2a 100644 --- a/x-pack/plugins/canvas/public/transitions/rotate/index.ts +++ b/x-pack/plugins/canvas/public/transitions/rotate/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -import './rotate.scss'; - import { TransitionStrings } from '../../../i18n'; const { rotate: strings } = TransitionStrings; diff --git a/x-pack/plugins/canvas/public/transitions/slide/index.ts b/x-pack/plugins/canvas/public/transitions/slide/index.ts index 0c3f82a09dd02..1cf87acca2963 100644 --- a/x-pack/plugins/canvas/public/transitions/slide/index.ts +++ b/x-pack/plugins/canvas/public/transitions/slide/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -import './slide.scss'; - import { TransitionStrings } from '../../../i18n'; const { slide: strings } = TransitionStrings; diff --git a/x-pack/plugins/canvas/public/transitions/zoom/index.ts b/x-pack/plugins/canvas/public/transitions/zoom/index.ts index c7c1db25bd0d7..c102935b4118b 100644 --- a/x-pack/plugins/canvas/public/transitions/zoom/index.ts +++ b/x-pack/plugins/canvas/public/transitions/zoom/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -import './zoom.scss'; - import { TransitionStrings } from '../../../i18n'; const { zoom: strings } = TransitionStrings; From 3838bfd34a68c6e3ef5757441786b0e19c96773d Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 24 Jun 2021 18:53:03 -0500 Subject: [PATCH 18/40] [Enterprise Search] Add notices for deactivated users and SMTP callout (#103285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Port #3904 to Kibana https://github.com/elastic/ent-search/pull/3904 * DRY out logic interfaces Should have done this long ago * Port #3920 to Kibana https://github.com/elastic/ent-search/pull/3920 * Lint fixes * Remove error state from form We already did this for the users flyout. Basically changes the dirty state of the form from an error state to just showing “Required”. i18n had not been translated yet for `ATTRIBUTE_VALUE_ERROR` * Add loading states * Remove manual disabling of button Co-authored-by: Constance * Remove manual disabling of other button * Lint fixes Co-authored-by: Constance --- .../components/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 86 ++++++------------- .../components/role_mappings/user.test.tsx | 24 +++++- .../components/role_mappings/user.tsx | 11 +++ .../__mocks__/elasticsearch_users.ts | 1 + .../role_mapping/attribute_selector.test.tsx | 11 ++- .../role_mapping/attribute_selector.tsx | 5 +- .../shared/role_mapping/constants.ts | 40 +++++++-- .../deactivated_user_callout.test.tsx | 75 ++++++++++++++++ .../role_mapping/deactivated_user_callout.tsx | 28 ++++++ .../applications/shared/role_mapping/index.ts | 2 + .../role_mapping/role_mapping_flyout.test.tsx | 1 + .../role_mapping/role_mapping_flyout.tsx | 3 + .../applications/shared/role_mapping/types.ts | 66 ++++++++++++++ .../shared/role_mapping/user_flyout.test.tsx | 1 + .../shared/role_mapping/user_flyout.tsx | 4 +- .../role_mapping/user_selector.test.tsx | 3 +- .../shared/role_mapping/user_selector.tsx | 18 +++- .../shared/role_mapping/users_table.test.tsx | 20 ++++- .../shared/role_mapping/users_table.tsx | 10 ++- .../public/applications/shared/types.ts | 3 +- .../views/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 84 ++++++------------ .../views/role_mappings/user.test.tsx | 24 +++++- .../views/role_mappings/user.tsx | 11 +++ 27 files changed, 406 insertions(+), 135 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/types.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx index dbebd8e46a219..02ff4c0ba1c3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -45,6 +45,7 @@ export const RoleMapping: React.FC = () => { selectedEngines, selectedAuthProviders, roleMappingErrors, + formLoading, } = useValues(RoleMappingsLogic); const isNew = !roleMapping; @@ -67,6 +68,7 @@ export const RoleMapping: React.FC = () => { return ( { userCreated: false, userFormIsNewUser: true, userFormUserIsExisting: true, + smtpSettingsPresent: false, + formLoading: false, }; const mappingsServerProps = { @@ -70,6 +72,7 @@ describe('RoleMappingsLogic', () => { hasAdvancedRoles: false, singleUserRoleMappings: [asSingleUserRoleMapping], elasticsearchUsers, + smtpSettingsPresent: false, }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts index 0b57e1d08a294..fb574a3588989 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts @@ -7,14 +7,17 @@ import { kea, MakeLogicType } from 'kea'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; - import { clearFlashMessages, flashAPIErrors, setSuccessMessage, } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; +import { + RoleMappingsBaseServerDetails, + RoleMappingsBaseActions, + RoleMappingsBaseValues, +} from '../../../shared/role_mapping'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types'; import { ASRoleMapping, RoleTypes } from '../../types'; @@ -29,16 +32,11 @@ import { type UserMapping = SingleUserRoleMapping; -interface RoleMappingsServerDetails { +interface RoleMappingsServerDetails extends RoleMappingsBaseServerDetails { roleMappings: ASRoleMapping[]; - attributes: string[]; - authProviders: string[]; availableEngines: Engine[]; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - hasAdvancedRoles: boolean; - multipleAuthProvidersConfig: boolean; singleUserRoleMappings: UserMapping[]; + hasAdvancedRoles: boolean; } const getFirstAttributeName = (roleMapping: ASRoleMapping) => @@ -47,24 +45,7 @@ const getFirstAttributeValue = (roleMapping: ASRoleMapping) => Object.entries(roleMapping.rules)[0][1] as AttributeName; const emptyUser = { username: '', email: '' } as ElasticsearchUser; -interface RoleMappingsActions { - handleAccessAllEnginesChange(selected: boolean): { selected: boolean }; - handleAuthProviderChange(value: string[]): { value: string[] }; - handleAttributeSelectorChange( - value: AttributeName, - firstElasticsearchRole: string - ): { value: AttributeName; firstElasticsearchRole: string }; - handleAttributeValueChange(value: string): { value: string }; - handleDeleteMapping(roleMappingId: string): { roleMappingId: string }; - handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] }; - handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes }; - handleUsernameSelectChange(username: string): { username: string }; - handleSaveMapping(): void; - handleSaveUser(): void; - initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeRoleMappings(): void; - resetState(): void; +interface RoleMappingsActions extends RoleMappingsBaseActions { setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping }; setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; setRoleMappings({ @@ -73,34 +54,14 @@ interface RoleMappingsActions { roleMappings: ASRoleMapping[]; }): { roleMappings: ASRoleMapping[] }; setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; - setElasticsearchUser( - elasticsearchUser?: ElasticsearchUser - ): { elasticsearchUser: ElasticsearchUser }; - openRoleMappingFlyout(): void; - openSingleUserRoleMappingFlyout(): void; - closeUsersAndRolesFlyout(): void; - setRoleMappingErrors(errors: string[]): { errors: string[] }; - enableRoleBasedAccess(): void; - setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean }; - setElasticsearchUsernameValue(username: string): { username: string }; - setElasticsearchEmailValue(email: string): { email: string }; - setUserCreated(): void; - setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean }; + handleAccessAllEnginesChange(selected: boolean): { selected: boolean }; + handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] }; + handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes }; } -interface RoleMappingsValues { +interface RoleMappingsValues extends RoleMappingsBaseValues { accessAllEngines: boolean; - attributeName: AttributeName; - attributeValue: string; - attributes: string[]; - availableAuthProviders: string[]; availableEngines: Engine[]; - dataLoading: boolean; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchUser: ElasticsearchUser; - hasAdvancedRoles: boolean; - multipleAuthProvidersConfig: boolean; roleMapping: ASRoleMapping | null; roleMappings: ASRoleMapping[]; singleUserRoleMapping: UserMapping | null; @@ -108,13 +69,7 @@ interface RoleMappingsValues { roleType: RoleTypes; selectedAuthProviders: string[]; selectedEngines: Set; - roleMappingFlyoutOpen: boolean; - singleUserRoleMappingFlyoutOpen: boolean; - selectedOptions: EuiComboBoxOptionOption[]; - roleMappingErrors: string[]; - userFormUserIsExisting: boolean; - userCreated: boolean; - userFormIsNewUser: boolean; + hasAdvancedRoles: boolean; } export const RoleMappingsLogic = kea>({ @@ -369,6 +324,21 @@ export const RoleMappingsLogic = kea userFormIsNewUser, }, ], + smtpSettingsPresent: [ + false, + { + setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent, + }, + ], + formLoading: [ + false, + { + handleSaveMapping: () => true, + handleSaveUser: () => true, + initializeRoleMappings: () => false, + setRoleMappingErrors: () => false, + }, + ], }, selectors: ({ selectors }) => ({ selectedOptions: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx index 88103532bd149..cec7f1541a31a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx @@ -14,7 +14,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping'; +import { + UserFlyout, + UserAddedInfo, + UserInvitationCallout, + DeactivatedUserCallout, +} from '../../../shared/role_mapping'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; @@ -91,6 +96,23 @@ describe('User', () => { expect(wrapper.find(UserAddedInfo)).toHaveLength(1); }); + it('renders DeactivatedUserCallout', () => { + setMockValues({ + ...mockValues, + singleUserRoleMapping: { + ...wsSingleUserRoleMapping, + invitation: null, + elasticsearchUser: { + ...wsSingleUserRoleMapping.elasticsearchUser, + enabled: false, + }, + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1); + }); + it('disables form when username value not present', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx index df231fac64df7..018d29706b05f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx @@ -17,6 +17,7 @@ import { UserSelector, UserAddedInfo, UserInvitationCallout, + DeactivatedUserCallout, } from '../../../shared/role_mapping'; import { RoleTypes } from '../../types'; @@ -48,6 +49,8 @@ export const User: React.FC = () => { roleMappingErrors, userCreated, userFormIsNewUser, + smtpSettingsPresent, + formLoading, } = useValues(RoleMappingsLogic); const roleTypes = hasAdvancedRoles ? [...standardRoles, ...advancedRoles] : standardRoles; @@ -55,6 +58,11 @@ export const User: React.FC = () => { const showEngineAssignmentSelector = hasEngines && hasAdvancedRoles; const flyoutDisabled = !userFormUserIsExisting && (!elasticsearchUser.email || !elasticsearchUser.username); + const userIsDeactivated = !!( + singleUserRoleMapping && + !singleUserRoleMapping.invitation && + !singleUserRoleMapping.elasticsearchUser.enabled + ); const userAddedInfo = singleUserRoleMapping && ( { 0} error={roleMappingErrors}> { return ( { > {userCreated ? userAddedInfo : createUserForm} {userInvitationCallout} + {userIsDeactivated && } ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts index 500f560675679..6d9365d63c320 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts @@ -9,5 +9,6 @@ export const elasticsearchUsers = [ { email: 'user1@user.com', username: 'user1', + enabled: true, }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx index d19d090b6e706..8fed459b0a8dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiComboBox, EuiFieldText } from '@elastic/eui'; +import { EuiComboBox, EuiFieldText, EuiFormRow } from '@elastic/eui'; import { AttributeName } from '../types'; import { AttributeSelector } from './attribute_selector'; -import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants'; +import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, REQUIRED_LABEL } from './constants'; const handleAttributeSelectorChange = jest.fn(); const handleAttributeValueChange = jest.fn(); @@ -166,5 +166,12 @@ describe('AttributeSelector', () => { baseProps.elasticsearchRoles[0] ); }); + + it('shows helpText when attributeValueInvalid', () => { + const wrapper = shallow(); + const formRow = wrapper.find(EuiFormRow).at(2); + + expect(formRow.prop('helpText')).toEqual(REQUIRED_LABEL); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index bedb6b3df90f2..e24bc03bea452 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -21,7 +21,7 @@ import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, ATTRIBUTE_VALUE_LABEL, - ATTRIBUTE_VALUE_ERROR, + REQUIRED_LABEL, AUTH_ANY_PROVIDER_LABEL, AUTH_INDIVIDUAL_PROVIDER_LABEL, AUTH_PROVIDER_LABEL, @@ -129,8 +129,7 @@ export const AttributeSelector: React.FC = ({ {attributeName === 'role' ? ( { + it('renders with new', () => { + const wrapper = shallow(); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + + User deactivated + + + + + This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. + + + + `); + }); + + it('renders with existing', () => { + const wrapper = shallow(); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + + + User deactivated + + + + + This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. + + + + `); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx new file mode 100644 index 0000000000000..5b69420d169ce --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiSpacer, EuiText, EuiIcon } from '@elastic/eui'; + +interface Props { + isNew: boolean; +} + +import { DEACTIVATED_USER_CALLOUT_LABEL, DEACTIVATED_USER_CALLOUT_DESCRIPTION } from './constants'; + +export const DeactivatedUserCallout: React.FC = ({ isNew }) => ( + <> + {!isNew && } + + {DEACTIVATED_USER_CALLOUT_LABEL} + + + {DEACTIVATED_USER_CALLOUT_DESCRIPTION} + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts index 8096b86939ff3..c10fc5c9d8242 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts @@ -5,7 +5,9 @@ * 2.0. */ +export * from './types'; export { AttributeSelector } from './attribute_selector'; +export { DeactivatedUserCallout } from './deactivated_user_callout'; export { RolesEmptyPrompt } from './roles_empty_prompt'; export { RoleMappingsTable } from './role_mappings_table'; export { RoleOptionLabel } from './role_option_label'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx index ffcf5508233fc..651a46f5df85e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx @@ -26,6 +26,7 @@ describe('RoleMappingFlyout', () => { const props = { isNew: true, disabled: false, + formLoading: false, closeUsersAndRolesFlyout, handleSaveMapping, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx index 4416a2de28011..bcaf26ccf2cfc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx @@ -36,6 +36,7 @@ interface Props { children: React.ReactNode; isNew: boolean; disabled: boolean; + formLoading: boolean; closeUsersAndRolesFlyout(): void; handleSaveMapping(): void; } @@ -44,6 +45,7 @@ export const RoleMappingFlyout: React.FC = ({ children, isNew, disabled, + formLoading, closeUsersAndRolesFlyout, handleSaveMapping, }) => ( @@ -78,6 +80,7 @@ export const RoleMappingFlyout: React.FC = ({ { isNew: true, isComplete: false, disabled: false, + formLoading: false, closeUserFlyout, handleSaveUser, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx index a3be5e295ddfe..8741a2b4517d3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx @@ -28,6 +28,7 @@ interface Props { isNew: boolean; isComplete: boolean; disabled: boolean; + formLoading: boolean; closeUserFlyout(): void; handleSaveUser(): void; } @@ -49,6 +50,7 @@ export const UserFlyout: React.FC = ({ isNew, isComplete, disabled, + formLoading, closeUserFlyout, handleSaveUser, }) => { @@ -75,7 +77,7 @@ export const UserFlyout: React.FC = ({ {CANCEL_BUTTON_LABEL} - + {isNew ? ADD_USER_LABEL : UPDATE_USER_LABEL} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx index 60bac97d09835..0aea55a51040c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx @@ -34,6 +34,7 @@ describe('UserSelector', () => { const props = { isNewUser: true, + smtpSettingsPresent: false, userFormUserIsExisting: true, elasticsearchUsers, elasticsearchUser: elasticsearchUsers[0], @@ -101,7 +102,7 @@ describe('UserSelector', () => { {...props} userFormUserIsExisting={false} elasticsearchUsers={[]} - elasticsearchUser={{ email: '', username: '' }} + elasticsearchUser={{ email: '', username: '', enabled: true }} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx index d65f97265f6a3..25aff5077c680 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiFieldText, + EuiLink, EuiRadio, EuiFormRow, EuiSelect, @@ -21,6 +22,9 @@ import { ElasticsearchUser } from '../../shared/types'; import { Role as WSRole } from '../../workplace_search/types'; import { USERNAME_LABEL, EMAIL_LABEL } from '../constants'; +import { docLinks } from '../doc_links'; + +const SMTP_URL = `${docLinks.enterpriseSearchBase}/mailer-configuration.html`; import { NEW_USER_LABEL, @@ -28,12 +32,15 @@ import { USERNAME_NO_USERS_TEXT, REQUIRED_LABEL, ROLE_LABEL, + SMTP_CALLOUT_LABEL, + SMTP_LINK_LABEL, } from './constants'; type SharedRole = WSRole | ASRole; interface Props { isNewUser: boolean; + smtpSettingsPresent: boolean; userFormUserIsExisting: boolean; elasticsearchUsers: ElasticsearchUser[]; elasticsearchUser: ElasticsearchUser; @@ -48,6 +55,7 @@ interface Props { export const UserSelector: React.FC = ({ isNewUser, + smtpSettingsPresent, userFormUserIsExisting, elasticsearchUsers, elasticsearchUser, @@ -66,6 +74,14 @@ export const UserSelector: React.FC = ({ })); const hasElasticsearchUsers = elasticsearchUsers.length > 0; const showNewUserExistingUserControls = userFormUserIsExisting && hasElasticsearchUsers; + const smptHelpText = !smtpSettingsPresent && ( + <> + {SMTP_CALLOUT_LABEL}{' '} + + {SMTP_LINK_LABEL} + + + ); const roleSelect = ( @@ -80,7 +96,7 @@ export const UserSelector: React.FC = ({ ); const emailInput = ( - + { singleUserRoleMappings: [wsSingleUserRoleMapping], initializeSingleUserRoleMapping, handleDeleteMapping, + enabled: true, }; it('renders', () => { @@ -55,6 +56,7 @@ describe('UsersTable', () => { elasticsearchUser: { email: null, username: 'foo', + enabled: true, }, }; const wrapper = mount(); @@ -97,4 +99,20 @@ describe('UsersTable', () => { `${engines[0].name}, ${engines[1].name} + 1` ); }); + + it('renders deactivatedBadge', () => { + const disabledUser = { + ...wsSingleUserRoleMapping, + elasticsearchUser: { + email: 'email@user.com', + username: 'foo', + enabled: false, + }, + invitation: null, + }; + const wrapper = mount(); + const cell = wrapper.find('[data-test-subj="UsernameCell"]'); + + expect(cell.find(EuiBadge)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx index 674796775b1d3..25a9eee38f93f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx @@ -15,6 +15,7 @@ import { WSRoleMapping } from '../../workplace_search/types'; import { INVITATION_PENDING_LABEL, + DEACTIVATED_LABEL, ALL_LABEL, FILTER_USERS_LABEL, NO_USERS_LABEL, @@ -37,6 +38,7 @@ interface SharedUser extends SingleUserRoleMapping—; const invitationBadge = {INVITATION_PENDING_LABEL}; +const deactivatedBadge = {DEACTIVATED_LABEL}; export const UsersTable: React.FC = ({ accessItemKey, @@ -63,6 +66,7 @@ export const UsersTable: React.FC = ({ const users = ((singleUserRoleMappings as SharedUser[]).map((user) => ({ username: user.elasticsearchUser.username, email: user.elasticsearchUser.email, + enabled: user.elasticsearchUser.enabled, roleType: user.roleMapping.roleType, id: user.roleMapping.id, accessItems: (user.roleMapping as SharedRoleMapping)[accessItemKey], @@ -73,7 +77,11 @@ export const UsersTable: React.FC = ({ { field: 'username', name: USERNAME_LABEL, - render: (_, { username }: SharedUser) => username, + render: (_, { username, invitation, enabled }: SharedUser) => ( +
    + {username} {!invitation && !enabled && deactivatedBadge} +
    + ), }, { field: 'email', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index e6d2c67d1baf8..5a90dd2c4a6bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -49,10 +49,11 @@ export interface Invitation { export interface ElasticsearchUser { email: string | null; username: string; + enabled: boolean; } export interface SingleUserRoleMapping { - invitation: Invitation; + invitation: Invitation | null; elasticsearchUser: ElasticsearchUser; roleMapping: T; } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index 20211d40d7010..734716af9f627 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -59,6 +59,7 @@ export const RoleMapping: React.FC = () => { selectedAuthProviders, roleMapping, roleMappingErrors, + formLoading, } = useValues(RoleMappingsLogic); const isNew = !roleMapping; @@ -69,6 +70,7 @@ export const RoleMapping: React.FC = () => { return ( { userCreated: false, userFormIsNewUser: true, userFormUserIsExisting: true, + smtpSettingsPresent: false, + formLoading: false, }; const roleGroup = { id: '123', @@ -76,6 +78,7 @@ describe('RoleMappingsLogic', () => { elasticsearchRoles: [], singleUserRoleMappings: [wsSingleUserRoleMapping], elasticsearchUsers, + smtpSettingsPresent: false, }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 7f26c8738786c..6e7104964cdb7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -7,14 +7,17 @@ import { kea, MakeLogicType } from 'kea'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; - import { clearFlashMessages, flashAPIErrors, setSuccessMessage, } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; +import { + RoleMappingsBaseServerDetails, + RoleMappingsBaseActions, + RoleMappingsBaseValues, +} from '../../../shared/role_mapping'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types'; import { RoleGroup, WSRoleMapping, Role } from '../../types'; @@ -28,14 +31,9 @@ import { type UserMapping = SingleUserRoleMapping; -interface RoleMappingsServerDetails { +interface RoleMappingsServerDetails extends RoleMappingsBaseServerDetails { roleMappings: WSRoleMapping[]; - attributes: string[]; - authProviders: string[]; availableGroups: RoleGroup[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchRoles: string[]; - multipleAuthProvidersConfig: boolean; singleUserRoleMappings: UserMapping[]; } @@ -45,24 +43,8 @@ const getFirstAttributeValue = (roleMapping: WSRoleMapping): string => Object.entries(roleMapping.rules)[0][1] as string; const emptyUser = { username: '', email: '' } as ElasticsearchUser; -interface RoleMappingsActions { - handleAllGroupsSelectionChange(selected: boolean): { selected: boolean }; - handleAuthProviderChange(value: string[]): { value: string[] }; - handleAttributeSelectorChange( - value: AttributeName, - firstElasticsearchRole: string - ): { value: AttributeName; firstElasticsearchRole: string }; - handleAttributeValueChange(value: string): { value: string }; - handleDeleteMapping(roleMappingId: string): { roleMappingId: string }; - handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] }; - handleRoleChange(roleType: Role): { roleType: Role }; - handleUsernameSelectChange(username: string): { username: string }; - handleSaveMapping(): void; - handleSaveUser(): void; - initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeRoleMappings(): void; - resetState(): void; +interface RoleMappingsActions extends RoleMappingsBaseActions { + setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] }; setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping }; setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; setRoleMappings({ @@ -71,34 +53,14 @@ interface RoleMappingsActions { roleMappings: WSRoleMapping[]; }): { roleMappings: WSRoleMapping[] }; setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; - setElasticsearchUser( - elasticsearchUser?: ElasticsearchUser - ): { elasticsearchUser: ElasticsearchUser }; - setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] }; - openRoleMappingFlyout(): void; - openSingleUserRoleMappingFlyout(): void; - closeUsersAndRolesFlyout(): void; - setRoleMappingErrors(errors: string[]): { errors: string[] }; - enableRoleBasedAccess(): void; - setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean }; - setElasticsearchUsernameValue(username: string): { username: string }; - setElasticsearchEmailValue(email: string): { email: string }; - setUserCreated(): void; - setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean }; + handleAllGroupsSelectionChange(selected: boolean): { selected: boolean }; + handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] }; + handleRoleChange(roleType: Role): { roleType: Role }; } -interface RoleMappingsValues { +interface RoleMappingsValues extends RoleMappingsBaseValues { includeInAllGroups: boolean; - attributeName: AttributeName; - attributeValue: string; - attributes: string[]; - availableAuthProviders: string[]; availableGroups: RoleGroup[]; - dataLoading: boolean; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchUser: ElasticsearchUser; - multipleAuthProvidersConfig: boolean; roleMapping: WSRoleMapping | null; roleMappings: WSRoleMapping[]; singleUserRoleMapping: UserMapping | null; @@ -106,13 +68,6 @@ interface RoleMappingsValues { roleType: Role; selectedAuthProviders: string[]; selectedGroups: Set; - roleMappingFlyoutOpen: boolean; - singleUserRoleMappingFlyoutOpen: boolean; - selectedOptions: EuiComboBoxOptionOption[]; - roleMappingErrors: string[]; - userFormUserIsExisting: boolean; - userCreated: boolean; - userFormIsNewUser: boolean; } export const RoleMappingsLogic = kea>({ @@ -369,6 +324,21 @@ export const RoleMappingsLogic = kea userFormIsNewUser, }, ], + smtpSettingsPresent: [ + false, + { + setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent, + }, + ], + formLoading: [ + false, + { + handleSaveMapping: () => true, + handleSaveUser: () => true, + initializeRoleMappings: () => false, + setRoleMappingErrors: () => false, + }, + ], }, selectors: ({ selectors }) => ({ selectedOptions: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx index 32ee1a7f22875..d8e1fc160901f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx @@ -14,7 +14,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping'; +import { + UserFlyout, + UserAddedInfo, + UserInvitationCallout, + DeactivatedUserCallout, +} from '../../../shared/role_mapping'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; @@ -90,6 +95,23 @@ describe('User', () => { expect(wrapper.find(UserAddedInfo)).toHaveLength(1); }); + it('renders DeactivatedUserCallout', () => { + setMockValues({ + ...mockValues, + singleUserRoleMapping: { + ...wsSingleUserRoleMapping, + invitation: null, + elasticsearchUser: { + ...wsSingleUserRoleMapping.elasticsearchUser, + enabled: false, + }, + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1); + }); + it('disables form when username value not present', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx index bfb32ee31c121..6447f43e6ed4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx @@ -17,6 +17,7 @@ import { UserSelector, UserAddedInfo, UserInvitationCallout, + DeactivatedUserCallout, } from '../../../shared/role_mapping'; import { Role } from '../../types'; @@ -46,12 +47,19 @@ export const User: React.FC = () => { roleMappingErrors, userCreated, userFormIsNewUser, + smtpSettingsPresent, + formLoading, } = useValues(RoleMappingsLogic); const showGroupAssignmentSelector = availableGroups.length > 0; const hasAvailableUsers = elasticsearchUsers.length > 0; const flyoutDisabled = (!userFormUserIsExisting || !hasAvailableUsers) && !elasticsearchUser.username; + const userIsDeactivated = !!( + singleUserRoleMapping && + !singleUserRoleMapping.invitation && + !singleUserRoleMapping.elasticsearchUser.enabled + ); const userAddedInfo = singleUserRoleMapping && ( { 0} error={roleMappingErrors}> { return ( { > {userCreated ? userAddedInfo : createUserForm} {userInvitationCallout} + {userIsDeactivated && } ); }; From 41b015abc90f89d7b0b3466ba4bdbd3d9092a388 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:23:13 -0400 Subject: [PATCH 19/40] [Security Solution] Correct linux OS lookup for Endpoint Exceptions (#103038) --- .../exceptionable_endpoint_fields.json | 20 ---- .../exceptionable_windows_mac_fields.json | 18 +++- .../components/exceptions/helpers.test.tsx | 11 --- .../common/components/exceptions/helpers.tsx | 99 +++++++++++-------- 4 files changed, 76 insertions(+), 72 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json index d5134945441f5..b5480aac27f67 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json @@ -1,19 +1,11 @@ [ "Endpoint.policy.applied.id", - "Target.process.Ext.code_signature.status", - "Target.process.Ext.code_signature.subject_name", - "Target.process.Ext.code_signature.trusted", - "Target.process.Ext.code_signature.valid", "Target.process.Ext.services", "Target.process.Ext.user", "Target.process.hash.md5", "Target.process.hash.sha1", "Target.process.hash.sha256", "Target.process.hash.sha512", - "Target.process.parent.Ext.code_signature.status", - "Target.process.parent.Ext.code_signature.subject_name", - "Target.process.parent.Ext.code_signature.trusted", - "Target.process.parent.Ext.code_signature.valid", "Target.process.parent.hash.md5", "Target.process.parent.hash.sha1", "Target.process.parent.hash.sha256", @@ -38,10 +30,6 @@ "event.outcome", "event.provider", "event.type", - "file.Ext.code_signature.status", - "file.Ext.code_signature.subject_name", - "file.Ext.code_signature.trusted", - "file.Ext.code_signature.valid", "file.attributes", "file.device", "file.directory", @@ -78,20 +66,12 @@ "host.os.platform", "host.os.version", "host.type", - "process.Ext.code_signature.status", - "process.Ext.code_signature.subject_name", - "process.Ext.code_signature.trusted", - "process.Ext.code_signature.valid", "process.Ext.services", "process.Ext.user", "process.hash.md5", "process.hash.sha1", "process.hash.sha256", "process.hash.sha512", - "process.parent.Ext.code_signature.status", - "process.parent.Ext.code_signature.subject_name", - "process.parent.Ext.code_signature.trusted", - "process.parent.Ext.code_signature.valid", "process.parent.hash.md5", "process.parent.hash.sha1", "process.parent.hash.sha256", diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_windows_mac_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_windows_mac_fields.json index 31784bb9c764a..dc21434f96b5a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_windows_mac_fields.json +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_windows_mac_fields.json @@ -18,5 +18,21 @@ "process.parent.executable.caseless", "process.parent.name.caseless", "process.parent.working_directory.caseless", - "process.working_directory.caseless" + "process.working_directory.caseless", + "Target.process.Ext.code_signature.status", + "Target.process.Ext.code_signature.subject_name", + "Target.process.Ext.code_signature.trusted", + "Target.process.Ext.code_signature.valid", + "Target.process.parent.Ext.code_signature.status", + "Target.process.parent.Ext.code_signature.subject_name", + "Target.process.parent.Ext.code_signature.trusted", + "Target.process.parent.Ext.code_signature.valid", + "file.Ext.code_signature.status", + "file.Ext.code_signature.subject_name", + "file.Ext.code_signature.trusted", + "file.Ext.code_signature.valid", + "process.parent.Ext.code_signature.status", + "process.parent.Ext.code_signature.subject_name", + "process.parent.Ext.code_signature.trusted", + "process.parent.Ext.code_signature.valid" ] \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 383b177d40c64..0af83e2cff3b5 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -87,17 +87,6 @@ const mockLinuxEndpointFields = [ aggregatable: false, readFromDocValues: false, }, - { - name: 'file.Ext.code_signature.status', - type: 'string', - esTypes: ['text'], - count: 0, - scripted: false, - searchable: true, - aggregatable: false, - readFromDocValues: false, - subType: { nested: { path: 'file.Ext.code_signature' } }, - }, ]; export const getEndpointField = (name: string) => diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 20413a6493661..bfb5c7298f330 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -236,7 +236,10 @@ export const enrichExceptionItemsWithOS = ( export const retrieveAlertOsTypes = (alertData?: AlertData): OsTypeArray => { const osDefaults: OsTypeArray = ['windows', 'macos']; if (alertData != null) { - const os = alertData.host && alertData.host.os && alertData.host.os.family; + const os = + alertData?.agent?.type === 'endpoint' + ? alertData.host?.os?.name?.toLowerCase() + : alertData.host?.os?.family; if (os != null) { return osType.is(os) ? [os] : osDefaults; } @@ -361,48 +364,64 @@ export const getPrepopulatedEndpointException = ({ const { file, host } = alertEcsData; const filePath = file?.path ?? ''; const sha256Hash = file?.hash?.sha256 ?? ''; - const filePathDefault = host?.os?.family === 'linux' ? 'file.path' : 'file.path.caseless'; + const isLinux = host?.os?.name === 'Linux'; + + const commonFields: Array<{ + field: string; + operator: 'excluded' | 'included'; + type: 'match'; + value: string; + }> = [ + { + field: isLinux ? 'file.path' : 'file.path.caseless', + operator: 'included', + type: 'match', + value: filePath ?? '', + }, + { + field: 'file.hash.sha256', + operator: 'included', + type: 'match', + value: sha256Hash ?? '', + }, + { + field: 'event.code', + operator: 'included', + type: 'match', + value: eventCode ?? '', + }, + ]; + const entriesToAdd = () => { + if (isLinux) { + return addIdToEntries(commonFields); + } else { + return addIdToEntries([ + { + field: 'file.Ext.code_signature', + type: 'nested', + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.subjectName : '', + }, + { + field: 'trusted', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.trusted : '', + }, + ], + }, + ...commonFields, + ]); + } + }; return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), - entries: addIdToEntries([ - { - field: 'file.Ext.code_signature', - type: 'nested', - entries: [ - { - field: 'subject_name', - operator: 'included', - type: 'match', - value: codeSignature != null ? codeSignature.subjectName : '', - }, - { - field: 'trusted', - operator: 'included', - type: 'match', - value: codeSignature != null ? codeSignature.trusted : '', - }, - ], - }, - { - field: filePathDefault, - operator: 'included', - type: 'match', - value: filePath ?? '', - }, - { - field: 'file.hash.sha256', - operator: 'included', - type: 'match', - value: sha256Hash ?? '', - }, - { - field: 'event.code', - operator: 'included', - type: 'match', - value: eventCode ?? '', - }, - ]), + entries: entriesToAdd(), }; }; From 4b20ff3bad9f73a4c08973d03295c2dfba754776 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 24 Jun 2021 20:25:26 -0600 Subject: [PATCH 20/40] [Maps] Add capability to delete features from layer & index (#103145) --- x-pack/plugins/maps/common/constants.ts | 1 + .../maps/public/actions/map_actions.ts | 19 +++++++ .../layers/vector_layer/vector_layer.tsx | 6 ++ .../es_search_source/es_search_source.tsx | 7 ++- .../es_search_source/util/feature_edit.ts | 10 ++++ .../mvt_single_layer_vector_source.tsx | 4 ++ .../sources/vector_source/vector_source.tsx | 5 ++ .../classes/util/mb_filter_expressions.ts | 2 +- .../mb_map/draw_control/draw_control.tsx | 14 +++++ .../draw_feature_control.tsx | 57 ++++++++++++++++++- .../draw_feature_control/index.ts | 9 ++- .../mb_map/draw_control/draw_tooltip.tsx | 4 ++ .../feature_edit_tools/feature_edit_tools.tsx | 19 +++++++ .../server/data_indexing/indexing_routes.ts | 53 +++++++++++++++++ .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- .../apis/maps/delete_feature.js | 42 ++++++++++++++ .../test/api_integration/apis/maps/index.js | 1 + .../es_archives/maps/data/data.json | 36 ++++++++++++ .../es_archives/maps/data/mappings.json | 28 +++++++++ 20 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 x-pack/test/api_integration/apis/maps/delete_feature.js diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index fa065e701184e..c16579cc142f0 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -169,6 +169,7 @@ export enum DRAW_SHAPE { POINT = 'POINT', LINE = 'LINE', SIMPLE_SELECT = 'SIMPLE_SELECT', + DELETE = 'DELETE', } export const AGG_DELIMITER = '_of_'; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 9d0d27496da92..464e4dbc6d5ae 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -376,3 +376,22 @@ export function addNewFeatureToIndex(geometry: Geometry | Position[]) { await dispatch(syncDataForLayer(layer, true)); }; } + +export function deleteFeatureFromIndex(featureId: string) { + return async ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + const editState = getEditState(getState()); + const layerId = editState ? editState.layerId : undefined; + if (!layerId) { + return; + } + const layer = getLayerById(layerId, getState()); + if (!layer || !(layer instanceof VectorLayer)) { + return; + } + await layer.deleteFeature(featureId); + await dispatch(syncDataForLayer(layer, true)); + }; +} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 49a0878ef80b2..7a6d91a71db42 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -99,6 +99,7 @@ export interface IVectorLayer extends ILayer { supportsFeatureEditing(): boolean; getLeftJoinFields(): Promise; addFeature(geometry: Geometry | Position[]): Promise; + deleteFeature(featureId: string): Promise; } export class VectorLayer extends AbstractLayer implements IVectorLayer { @@ -1156,4 +1157,9 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { const layerSource = this.getSource(); await layerSource.addFeature(geometry); } + + async deleteFeature(featureId: string) { + const layerSource = this.getSource(); + await layerSource.deleteFeature(featureId); + } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 9f7bd1260ca22..019c3c1b4943b 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -66,7 +66,7 @@ import { isValidStringConfig } from '../../util/valid_string_config'; import { TopHitsUpdateSourceEditor } from './top_hits'; import { getDocValueAndSourceFields, ScriptField } from './util/get_docvalue_source_fields'; import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; -import { addFeatureToIndex, getMatchingIndexes } from './util/feature_edit'; +import { addFeatureToIndex, deleteFeatureFromIndex, getMatchingIndexes } from './util/feature_edit'; export function timerangeToTimeextent(timerange: TimeRange): Timeslice | undefined { const timeRangeBounds = getTimeFilter().calculateBounds(timerange); @@ -716,6 +716,11 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye await addFeatureToIndex(indexPattern.title, geometry, this.getGeoFieldName()); } + async deleteFeature(featureId: string) { + const indexPattern = await this.getIndexPattern(); + await deleteFeatureFromIndex(indexPattern.title, featureId); + } + async getUrlTemplateWithMeta( searchFilters: VectorSourceRequestMeta ): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts index ac8e2ba42f282..f306a225df69a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts @@ -26,6 +26,16 @@ export const addFeatureToIndex = async ( }); }; +export const deleteFeatureFromIndex = async (indexName: string, featureId: string) => { + return await getHttp().fetch({ + path: `${INDEX_FEATURE_PATH}/${featureId}`, + method: 'DELETE', + body: JSON.stringify({ + index: indexName, + }), + }); +}; + export const getMatchingIndexes = async (indexPattern: string) => { return await getHttp().fetch({ path: `${GET_MATCHING_INDEXES_PATH}/${indexPattern}`, diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 5bf7a2e47cc66..f825a85f50bbd 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -102,6 +102,10 @@ export class MVTSingleLayerVectorSource throw new Error('Does not implement addFeature'); } + deleteFeature(featureId: string): Promise { + throw new Error('Does not implement deleteFeature'); + } + getMVTFields(): MVTField[] { return this._descriptor.fields.map((field: MVTFieldDescriptor) => { return new MVTField({ diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 8f93de705e365..f006fa7fde3a4 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -69,6 +69,7 @@ export interface IVectorSource extends ISource { getTimesliceMaskFieldName(): Promise; supportsFeatureEditing(): Promise; addFeature(geometry: Geometry | Position[]): Promise; + deleteFeature(featureId: string): Promise; } export class AbstractVectorSource extends AbstractSource implements IVectorSource { @@ -165,6 +166,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc throw new Error('Should implement VectorSource#addFeature'); } + async deleteFeature(featureId: string): Promise { + throw new Error('Should implement VectorSource#deleteFeature'); + } + async supportsFeatureEditing(): Promise { return false; } diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index 6a193216c7c1e..9568ef5c35bb1 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -20,7 +20,7 @@ export interface TimesliceMaskConfig { } export const EXCLUDE_TOO_MANY_FEATURES_BOX = ['!=', ['get', KBN_TOO_MANY_FEATURES_PROPERTY], true]; -const EXCLUDE_CENTROID_FEATURES = ['!=', ['get', KBN_IS_CENTROID_FEATURE], true]; +export const EXCLUDE_CENTROID_FEATURES = ['!=', ['get', KBN_IS_CENTROID_FEATURE], true]; function getFilterExpression( filters: unknown[], diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx index 5d9cb59bbe522..66f0e0c4a9515 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx @@ -13,6 +13,7 @@ import MapboxDraw from '@mapbox/mapbox-gl-draw'; import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; import type { Map as MbMap } from '@kbn/mapbox-gl'; import { Feature } from 'geojson'; +import { MapMouseEvent } from '@kbn/mapbox-gl'; import { DRAW_SHAPE } from '../../../../common/constants'; import { DrawCircle, DRAW_CIRCLE_RADIUS_MB_FILTER } from './draw_circle'; import { DrawTooltip } from './draw_tooltip'; @@ -37,6 +38,7 @@ mbDrawModes[DRAW_CIRCLE] = DrawCircle; export interface Props { drawShape?: DRAW_SHAPE; onDraw: (event: { features: Feature[] }, drawControl?: MapboxDraw) => void; + onClick?: (event: MapMouseEvent, drawControl?: MapboxDraw) => void; mbMap: MbMap; enable: boolean; updateEditShape: (shapeToDraw: DRAW_SHAPE) => void; @@ -68,6 +70,12 @@ export class DrawControl extends Component { this.props.onDraw(event, this._mbDrawControl); }; + _onClick = (event: MapMouseEvent) => { + if (this.props.onClick) { + this.props.onClick(event, this._mbDrawControl); + } + }; + // debounce with zero timeout needed to allow mapbox-draw finish logic to complete // before _removeDrawControl is called _syncDrawControl = _.debounce(() => { @@ -96,6 +104,9 @@ export class DrawControl extends Component { this.props.mbMap.getCanvas().style.cursor = ''; this.props.mbMap.off('draw.modechange', this._onModeChange); this.props.mbMap.off('draw.create', this._onDraw); + if (this.props.onClick) { + this.props.mbMap.off('click', this._onClick); + } this.props.mbMap.removeLayer(GL_DRAW_RADIUS_LABEL_LAYER_ID); this.props.mbMap.removeControl(this._mbDrawControl); this._mbDrawControlAdded = false; @@ -131,6 +142,9 @@ export class DrawControl extends Component { this.props.mbMap.getCanvas().style.cursor = 'crosshair'; this.props.mbMap.on('draw.modechange', this._onModeChange); this.props.mbMap.on('draw.create', this._onDraw); + if (this.props.onClick) { + this.props.mbMap.on('click', this._onClick); + } } const { DRAW_LINE_STRING, DRAW_POLYGON, DRAW_POINT, SIMPLE_SELECT } = this._mbDrawControl.modes; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx index fb595e7804dfe..eb5ea9b5ddba5 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx @@ -6,26 +6,34 @@ */ import React, { Component } from 'react'; -import { Map as MbMap } from 'mapbox-gl'; +import { Map as MbMap, Point as MbPoint } from 'mapbox-gl'; // @ts-expect-error import MapboxDraw from '@mapbox/mapbox-gl-draw'; import { Feature, Geometry, Position } from 'geojson'; import { i18n } from '@kbn/i18n'; // @ts-expect-error import * as jsts from 'jsts'; +import { MapMouseEvent } from '@kbn/mapbox-gl'; import { getToasts } from '../../../../kibana_services'; import { DrawControl } from '../'; import { DRAW_MODE, DRAW_SHAPE } from '../../../../../common'; +import { ILayer } from '../../../../classes/layers/layer'; +import { + EXCLUDE_CENTROID_FEATURES, + EXCLUDE_TOO_MANY_FEATURES_BOX, +} from '../../../../classes/util/mb_filter_expressions'; const geoJSONReader = new jsts.io.GeoJSONReader(); export interface ReduxStateProps { drawShape?: DRAW_SHAPE; drawMode: DRAW_MODE; + editLayer: ILayer | undefined; } export interface ReduxDispatchProps { addNewFeatureToIndex: (geometry: Geometry | Position[]) => void; + deleteFeatureFromIndex: (featureId: string) => void; disableDrawState: () => void; } @@ -75,11 +83,58 @@ export class DrawFeatureControl extends Component { } }; + _onClick = async (event: MapMouseEvent, drawControl?: MapboxDraw) => { + const mbLngLatPoint: MbPoint = event.point; + if (!this.props.editLayer) { + return; + } + const mbEditLayerIds = this.props.editLayer + .getMbLayerIds() + .filter((mbLayerId) => !!this.props.mbMap.getLayer(mbLayerId)); + const PADDING = 2; // in pixels + const mbBbox = [ + { + x: mbLngLatPoint.x - PADDING, + y: mbLngLatPoint.y - PADDING, + }, + { + x: mbLngLatPoint.x + PADDING, + y: mbLngLatPoint.y + PADDING, + }, + ] as [MbPoint, MbPoint]; + const selectedFeatures = this.props.mbMap.queryRenderedFeatures(mbBbox, { + layers: mbEditLayerIds, + filter: ['all', EXCLUDE_TOO_MANY_FEATURES_BOX, EXCLUDE_CENTROID_FEATURES], + }); + if (!selectedFeatures.length) { + return; + } + const topMostFeature = selectedFeatures[0]; + + try { + if (!(topMostFeature.properties && topMostFeature.properties._id)) { + throw Error(`Associated Elasticsearch document id not found`); + } + const docId = topMostFeature.properties._id; + this.props.deleteFeatureFromIndex(docId); + } catch (error) { + getToasts().addWarning( + i18n.translate('xpack.maps.drawFeatureControl.unableToDeleteFeature', { + defaultMessage: `Unable to delete feature, error: '{errorMsg}'.`, + values: { + errorMsg: error.message, + }, + }) + ); + } + }; + render() { return ( diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts index 9034e40913e77..e1d703173fc2d 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts @@ -15,16 +15,18 @@ import { ReduxStateProps, OwnProps, } from './draw_feature_control'; -import { addNewFeatureToIndex, updateEditShape } from '../../../../actions'; +import { addNewFeatureToIndex, deleteFeatureFromIndex, updateEditShape } from '../../../../actions'; import { MapStoreState } from '../../../../reducers/store'; -import { getEditState } from '../../../../selectors/map_selectors'; +import { getEditState, getLayerById } from '../../../../selectors/map_selectors'; import { getDrawMode } from '../../../../selectors/ui_selectors'; function mapStateToProps(state: MapStoreState): ReduxStateProps { const editState = getEditState(state); + const editLayer = editState ? getLayerById(editState.layerId, state) : undefined; return { drawShape: editState ? editState.drawShape : undefined, drawMode: getDrawMode(state), + editLayer, }; } @@ -35,6 +37,9 @@ function mapDispatchToProps( addNewFeatureToIndex(geometry: Geometry | Position[]) { dispatch(addNewFeatureToIndex(geometry)); }, + deleteFeatureFromIndex(featureId: string) { + dispatch(deleteFeatureFromIndex(featureId)); + }, disableDrawState() { dispatch(updateEditShape(null)); }, diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx index 5321c30f75245..4122f7ea796d4 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.tsx @@ -98,6 +98,10 @@ export class DrawTooltip extends Component { instructions = i18n.translate('xpack.maps.drawTooltip.pointInstructions', { defaultMessage: 'Click to create point.', }); + } else if (this.props.drawShape === DRAW_SHAPE.DELETE) { + instructions = i18n.translate('xpack.maps.drawTooltip.deleteInstructions', { + defaultMessage: 'Click feature to delete.', + }); } else { // unknown draw type, tooltip not needed return null; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx index 66948c0fc9bca..a2b3b20ae1877 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx @@ -34,6 +34,7 @@ export function FeatureEditTools(props: Props) { const drawCircleSelected = props.drawShape === DRAW_SHAPE.DISTANCE; const drawBBoxSelected = props.drawShape === DRAW_SHAPE.BOUNDS; const drawPointSelected = props.drawShape === DRAW_SHAPE.POINT; + const deleteSelected = props.drawShape === DRAW_SHAPE.DELETE; return ( @@ -117,6 +118,24 @@ export function FeatureEditTools(props: Props) { isSelected={drawPointSelected} display={drawPointSelected ? 'fill' : 'empty'} /> + props.setDrawShape(DRAW_SHAPE.DELETE)} + iconType="trash" + aria-label={i18n.translate( + 'xpack.maps.toolbarOverlay.featureDraw.deletePointOrShapeLabel', + { + defaultMessage: 'Delete point or shape', + } + )} + title={i18n.translate('xpack.maps.toolbarOverlay.featureDraw.deletePointOrShapeTitle', { + defaultMessage: 'Delete point or shape', + })} + aria-pressed={deleteSelected} + isSelected={deleteSelected} + display={deleteSelected ? 'fill' : 'empty'} + /> { + try { + const { body: resp } = await context.core.elasticsearch.client.asCurrentUser.delete({ + index: request.body.index, + id: request.params.featureId, + refresh: true, + }); + if (resp.result === 'Error') { + throw resp; + } else { + return response.ok({ body: { success: true } }); + } + } catch (error) { + logger.error(error); + const errorStatusCode = error.meta?.statusCode; + if (errorStatusCode === 401) { + return response.unauthorized({ + body: { + message: 'User not authorized to delete indexed feature', + }, + }); + } else if (errorStatusCode === 403) { + return response.forbidden({ + body: { + message: 'Access to delete indexed feature forbidden', + }, + }); + } else if (errorStatusCode === 404) { + return response.notFound({ + body: { message: 'Feature not found' }, + }); + } else { + return response.custom({ + body: 'Unknown error deleting feature', + statusCode: 500, + }); + } + } + } + ); + router.get( { path: `${GET_MATCHING_INDEXES_PATH}/{indexPattern}`, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aab80fe308861..3cf0891bc73b2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -24284,4 +24284,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d4f263b58b8ab..3a1836cc25014 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24660,4 +24660,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/maps/delete_feature.js b/x-pack/test/api_integration/apis/maps/delete_feature.js new file mode 100644 index 0000000000000..0755b1a1f6b59 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/delete_feature.js @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('doc feature deletion', () => { + it('should delete a valid feature document', async () => { + await supertest + .delete(`/api/maps/feature/999`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'drawing_data', + }) + .expect(200); + }); + + it('previously deleted document no longer exists in index', async () => { + await supertest + .delete(`/api/maps/feature/999`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'drawing_data', + }) + .expect(404); + }); + + it('should fail if not a valid document', async () => { + await supertest + .delete(`/api/maps/feature/998`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'drawing_data', + }) + .expect(404); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index 14a42e06dbb9a..b3f1cf1a89e3b 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -17,6 +17,7 @@ export default function ({ loadTestFile, getService }) { describe('', () => { loadTestFile(require.resolve('./get_indexes_matching_pattern')); loadTestFile(require.resolve('./create_doc_source')); + loadTestFile(require.resolve('./delete_feature')); loadTestFile(require.resolve('./index_data')); loadTestFile(require.resolve('./fonts_api')); loadTestFile(require.resolve('./index_settings')); diff --git a/x-pack/test/functional/es_archives/maps/data/data.json b/x-pack/test/functional/es_archives/maps/data/data.json index e44b19504af38..86b257db8205c 100644 --- a/x-pack/test/functional/es_archives/maps/data/data.json +++ b/x-pack/test/functional/es_archives/maps/data/data.json @@ -137,6 +137,42 @@ } +{ + "type": "doc", + "value": { + "id": "999", + "index": "drawing_data", + "source": { + "geometry": { + "coordinates": [ + [ + [ + 100, + 5 + ], + [ + 95, + -5 + ], + [ + 105, + -5 + ], + [ + 100, + 5 + ] + ] + ], + "type": "polygon" + }, + "name": "doc-to-delete", + "prop1": 9 + } + } +} + + { "type": "doc", "value": { diff --git a/x-pack/test/functional/es_archives/maps/data/mappings.json b/x-pack/test/functional/es_archives/maps/data/mappings.json index 4ad5d6c33295b..3b674d162fced 100644 --- a/x-pack/test/functional/es_archives/maps/data/mappings.json +++ b/x-pack/test/functional/es_archives/maps/data/mappings.json @@ -26,6 +26,34 @@ } } +{ + "type": "index", + "value": { + "index": "drawing_data", + "mappings": { + "properties": { + "geometry": { + "type": "geo_shape" + }, + "name": { + "type": "keyword" + }, + "prop1": { + "type": "byte" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1", + "max_result_window": "10001", + "max_inner_result_window": "101" + } + } + } +} + { "type": "index", "value": { From bc8ba8314a615e1cbf54a48974f8d36c7b38b83b Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Fri, 25 Jun 2021 00:22:29 -0400 Subject: [PATCH 21/40] [Fleet] Add support for constant_keyword "value" in package field definitions (#103000) This enables Fleet to accept package field definitions that declare a constant "value" for `constant_keyword` fields. Fleet will generate an index template for the constant_keyword field that contains the `value` attribute. Relates: https://github.com/elastic/package-spec/pull/194 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/template.test.ts.snap | 4 ++++ .../elasticsearch/template/template.test.ts | 20 +++++++++++++++++++ .../epm/elasticsearch/template/template.ts | 6 ++++++ .../fields/__snapshots__/field.test.ts.snap | 5 +++++ .../fleet/server/services/epm/fields/field.ts | 1 + .../server/services/epm/fields/tests/base.yml | 3 +++ 6 files changed, 39 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap index 6a4476316bfa5..eff35a30ba2d6 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap @@ -86,6 +86,10 @@ exports[`EPM template tests loading base.yml: base.yml 1`] = ` }, "validarray": { "type": "integer" + }, + "cycle_type": { + "type": "constant_keyword", + "value": "bicycle" } }, "_meta": { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index d1f806f67ca5c..10db955c52ee1 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -613,6 +613,26 @@ describe('EPM template', () => { expect(JSON.stringify(mappings)).toEqual(JSON.stringify(constantKeywordMapping)); }); + it('tests constant_keyword field type with value', () => { + const constantKeywordLiteralYaml = ` +- name: constantKeyword + type: constant_keyword + value: always_the_same + `; + const constantKeywordMapping = { + properties: { + constantKeyword: { + type: 'constant_keyword', + value: 'always_the_same', + }, + }, + }; + const fields: Field[] = safeLoad(constantKeywordLiteralYaml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(JSON.stringify(mappings)).toEqual(JSON.stringify(constantKeywordMapping)); + }); + it('processes meta fields', () => { const metaFieldLiteralYaml = ` - name: fieldWithMetas diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 6aa7680395bed..d4181201677c5 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -150,6 +150,12 @@ export function generateMappings(fields: Field[]): IndexTemplateMappings { fieldProps.fields = generateMultiFields(field.multi_fields); } break; + case 'constant_keyword': + fieldProps.type = field.type; + if (field.value) { + fieldProps.value = field.value; + } + break; case 'object': fieldProps = { ...fieldProps, ...generateDynamicAndEnabled(field), type: 'object' }; break; diff --git a/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap index 5c402b896093a..78f1fbc528696 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap @@ -55,6 +55,11 @@ exports[`tests loading fields.yml: base.yml 1`] = ` "name": "validarray", "type": "array", "object_type": "integer" + }, + { + "name": "cycle_type", + "type": "constant_keyword", + "value": "bicycle" } ] `; diff --git a/x-pack/plugins/fleet/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts index b8839b88bb78c..9fa738aa60d09 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/field.ts +++ b/x-pack/plugins/fleet/server/services/epm/fields/field.ts @@ -15,6 +15,7 @@ export interface Field { name: string; type?: string; description?: string; + value?: string; format?: string; fields?: Fields; enabled?: boolean; diff --git a/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml index 5a71c7dee54dc..ee7cd63539c15 100644 --- a/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml +++ b/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml @@ -20,4 +20,7 @@ object_type: integer - name: invalidarray type: array +- name: cycle_type + type: constant_keyword + value: bicycle From bc6ee27a29c24582d9553813d4d84864749a7cc8 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 25 Jun 2021 07:55:06 +0200 Subject: [PATCH 22/40] Maps locators (#102810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 implement maps locator * feat: 🎸 add tile map locator * feat: 🎸 add region map locator * feat: 🎸 register maps locators * refactor: 💡 remove usage of mpas url gen, replace by locator * chore: 🤖 remove url generators * refactor: 💡 use locators in maps deprecation messages * chore: 🤖 remove maps url generators * refactor: 💡 use new property name * feat: 🎸 use constant Co-authored-by: Vadim Kibana Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/get_deprecation_message.tsx | 32 +-- .../public/get_deprecation_message.tsx | 30 +- ...url_generator.test.ts => locators.test.ts} | 85 +++--- x-pack/plugins/maps/public/locators.ts | 260 ++++++++++++++++++ x-pack/plugins/maps/public/plugin.ts | 36 +-- .../visualize_geo_field_action.ts | 35 ++- x-pack/plugins/maps/public/url_generator.ts | 241 ---------------- 7 files changed, 367 insertions(+), 352 deletions(-) rename x-pack/plugins/maps/public/{url_generator.test.ts => locators.test.ts} (52%) create mode 100644 x-pack/plugins/maps/public/locators.ts delete mode 100644 x-pack/plugins/maps/public/url_generator.ts diff --git a/src/plugins/region_map/public/get_deprecation_message.tsx b/src/plugins/region_map/public/get_deprecation_message.tsx index 5ae39a1291c4c..2606c8ed108e2 100644 --- a/src/plugins/region_map/public/get_deprecation_message.tsx +++ b/src/plugins/region_map/public/get_deprecation_message.tsx @@ -8,8 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UrlGeneratorContract } from 'src/plugins/share/public'; -import { getCoreService, getQueryService, getShareService } from './kibana_services'; +import { getQueryService, getShareService } from './kibana_services'; import { Vis } from '../../visualizations/public'; import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; @@ -25,24 +24,16 @@ function getEmsLayerId(id: string | number, layerId: string) { } export function getDeprecationMessage(vis: Vis) { - let mapsRegionMapUrlGenerator: - | UrlGeneratorContract<'MAPS_APP_REGION_MAP_URL_GENERATOR'> - | undefined; - try { - mapsRegionMapUrlGenerator = getShareService().urlGenerators.getUrlGenerator( - 'MAPS_APP_REGION_MAP_URL_GENERATOR' - ); - } catch (error) { - // ignore error thrown when url generator is not available - } - const title = i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }); async function onClick(e: React.MouseEvent) { e.preventDefault(); + const locator = getShareService().url.locators.get('MAPS_APP_REGION_MAP_LOCATOR'); + if (!locator) return; + const query = getQueryService(); - const createUrlParams: { [key: string]: any } = { + const params: { [key: string]: any } = { label: vis.title ? vis.title : title, emsLayerId: vis.params.selectedLayer.isEMS ? getEmsLayerId(vis.params.selectedLayer.id, vis.params.selectedLayer.layerId) @@ -59,23 +50,22 @@ export function getDeprecationMessage(vis: Vis) { const bucketAggs = vis.data?.aggs?.byType('buckets'); if (bucketAggs?.length && bucketAggs[0].type.dslName === 'terms') { - createUrlParams.termsFieldName = bucketAggs[0].getField()?.name; - createUrlParams.termsSize = bucketAggs[0].getParam('size'); + params.termsFieldName = bucketAggs[0].getField()?.name; + params.termsSize = bucketAggs[0].getParam('size'); } const metricAggs = vis.data?.aggs?.byType('metrics'); if (metricAggs?.length) { - createUrlParams.metricAgg = metricAggs[0].type.dslName; - createUrlParams.metricFieldName = metricAggs[0].getField()?.name; + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; } - const url = await mapsRegionMapUrlGenerator!.createUrl(createUrlParams); - getCoreService().application.navigateToUrl(url); + locator.navigate(params); } return ( diff --git a/src/plugins/tile_map/public/get_deprecation_message.tsx b/src/plugins/tile_map/public/get_deprecation_message.tsx index 592b2b5f36eb2..6f71aa15b8a6b 100644 --- a/src/plugins/tile_map/public/get_deprecation_message.tsx +++ b/src/plugins/tile_map/public/get_deprecation_message.tsx @@ -8,22 +8,12 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UrlGeneratorContract } from 'src/plugins/share/public'; -import { getCoreService, getQueryService, getShareService } from './services'; +import { getQueryService, getShareService } from './services'; import { indexPatterns } from '../../data/public'; import { Vis } from '../../visualizations/public'; import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; export function getDeprecationMessage(vis: Vis) { - let mapsTileMapUrlGenerator: UrlGeneratorContract<'MAPS_APP_TILE_MAP_URL_GENERATOR'> | undefined; - try { - mapsTileMapUrlGenerator = getShareService().urlGenerators.getUrlGenerator( - 'MAPS_APP_TILE_MAP_URL_GENERATOR' - ); - } catch (error) { - // ignore error thrown when url generator is not available - } - const title = i18n.translate('tileMap.vis.mapTitle', { defaultMessage: 'Coordinate Map', }); @@ -31,8 +21,11 @@ export function getDeprecationMessage(vis: Vis) { async function onClick(e: React.MouseEvent) { e.preventDefault(); + const locator = getShareService().url.locators.get('MAPS_APP_TILE_MAP_LOCATOR'); + if (!locator) return; + const query = getQueryService(); - const createUrlParams: { [key: string]: any } = { + const params: { [key: string]: any } = { label: vis.title ? vis.title : title, mapType: vis.params.mapType, colorSchema: vis.params.colorSchema, @@ -45,7 +38,7 @@ export function getDeprecationMessage(vis: Vis) { const bucketAggs = vis.data?.aggs?.byType('buckets'); if (bucketAggs?.length && bucketAggs[0].type.dslName === 'geohash_grid') { - createUrlParams.geoFieldName = bucketAggs[0].getField()?.name; + params.geoFieldName = bucketAggs[0].getField()?.name; } else if (vis.data.indexPattern) { // attempt to default to first geo point field when geohash is not configured yet const geoField = vis.data.indexPattern.fields.find((field) => { @@ -54,23 +47,22 @@ export function getDeprecationMessage(vis: Vis) { ); }); if (geoField) { - createUrlParams.geoFieldName = geoField.name; + params.geoFieldName = geoField.name; } } const metricAggs = vis.data?.aggs?.byType('metrics'); if (metricAggs?.length) { - createUrlParams.metricAgg = metricAggs[0].type.dslName; - createUrlParams.metricFieldName = metricAggs[0].getField()?.name; + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; } - const url = await mapsTileMapUrlGenerator!.createUrl(createUrlParams); - getCoreService().application.navigateToUrl(url); + locator.navigate(params); } return ( diff --git a/x-pack/plugins/maps/public/url_generator.test.ts b/x-pack/plugins/maps/public/locators.test.ts similarity index 52% rename from x-pack/plugins/maps/public/url_generator.test.ts rename to x-pack/plugins/maps/public/locators.test.ts index 827deb4cc9ad4..d6e82d1cdb601 100644 --- a/x-pack/plugins/maps/public/url_generator.test.ts +++ b/x-pack/plugins/maps/public/locators.test.ts @@ -5,49 +5,49 @@ * 2.0. */ -import { createMapsUrlGenerator } from './url_generator'; import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES } from '../common/constants'; import { esFilters } from '../../../../src/plugins/data/public'; +import { MapsAppLocatorDefinition } from './locators'; +import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import { LayerDescriptor } from '../common/descriptor_types'; -const APP_BASE_PATH: string = 'test/app/maps'; const MAP_ID: string = '2c9c1f60-1909-11e9-919b-ffe5949a18d2'; const LAYER_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647'; const INDEX_PATTERN_ID: string = '90943e30-9a47-11e8-b64d-95841ca0b247'; describe('visualize url generator', () => { test('creates a link to a new visualization', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({}); - expect(url).toMatchInlineSnapshot(`"test/app/maps/map#/?_g=()&_a=()"`); + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({}); + + expect(location).toMatchObject({ + app: 'maps', + path: '/map#/?_g=()&_a=()', + state: {}, + }); }); test('creates a link with global time range set up', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()"` - ); + + expect(location).toMatchObject({ + app: 'maps', + path: '/map#/?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()', + state: {}, + }); }); test('creates a link with initialLayers set up', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); const initialLayers = [ { id: LAYER_ID, @@ -64,22 +64,22 @@ describe('visualize url generator', () => { }, }, ]; - const url = await generator.createUrl!({ - initialLayers, + const location = await locator.getLocation({ + initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableState, + }); + + expect(location).toMatchObject({ + app: 'maps', + path: `/map#/?_g=()&_a=()&initialLayers=(id%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CsourceDescriptor%3A(geoField%3Atest%2Cid%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CindexPatternId%3A'90943e30-9a47-11e8-b64d-95841ca0b247'%2Clabel%3A'Sample%20Data'%2CscalingType%3ALIMIT%2CtooltipProperties%3A!()%2Ctype%3AES_SEARCH)%2Ctype%3AVECTOR%2Cvisible%3A!t)`, + state: {}, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/?_g=()&_a=()&initialLayers=(id%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CsourceDescriptor%3A(geoField%3Atest%2Cid%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CindexPatternId%3A'90943e30-9a47-11e8-b64d-95841ca0b247'%2Clabel%3A'Sample%20Data'%2CscalingType%3ALIMIT%2CtooltipProperties%3A!()%2Ctype%3AES_SEARCH)%2Ctype%3AVECTOR%2Cvisible%3A!t)"` - ); }); test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, refreshInterval: { pause: false, value: 300 }, mapId: MAP_ID, @@ -106,8 +106,11 @@ describe('visualize url generator', () => { ], query: { query: 'q2', language: 'kuery' }, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/${MAP_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))"` - ); + + expect(location).toMatchObject({ + app: 'maps', + path: `/map#/${MAP_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))`, + state: {}, + }); }); }); diff --git a/x-pack/plugins/maps/public/locators.ts b/x-pack/plugins/maps/public/locators.ts new file mode 100644 index 0000000000000..7e2be7c6c7ec9 --- /dev/null +++ b/x-pack/plugins/maps/public/locators.ts @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable max-classes-per-file */ + +import rison from 'rison-node'; +import type { + TimeRange, + Filter, + Query, + QueryState, + RefreshInterval, +} from '../../../../src/plugins/data/public'; +import { esFilters } from '../../../../src/plugins/data/public'; +import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; +import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import type { LocatorDefinition, LocatorPublic } from '../../../../src/plugins/share/public'; +import type { LayerDescriptor } from '../common/descriptor_types'; +import { INITIAL_LAYERS_KEY, APP_ID } from '../common/constants'; +import { lazyLoadMapModules } from './lazy_load_bundle'; + +export interface MapsAppLocatorParams extends SerializableState { + /** + * If given, it will load the given map else will load the create a new map page. + */ + mapId?: string; + + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the initial Layers. + */ + initialLayers?: LayerDescriptor[] & SerializableState; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval & SerializableState; + + /** + * Optionally apply filers. NOTE: if given and used in conjunction with `mapId`, and the + * saved map has filters saved with it, this will _replace_ those filters. + */ + filters?: Filter[]; + + /** + * Optionally set a query. NOTE: if given and used in conjunction with `mapId`, and the + * saved map has a query saved with it, this will _replace_ that query. + */ + query?: Query; + + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + hash?: boolean; +} + +export const MAPS_APP_LOCATOR = 'MAPS_APP_LOCATOR' as const; + +export type MapsAppLocator = LocatorPublic; + +export interface MapsAppLocatorDependencies { + useHash: boolean; +} + +export class MapsAppLocatorDefinition implements LocatorDefinition { + public readonly id = MAPS_APP_LOCATOR; + + constructor(protected readonly deps: MapsAppLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppLocatorParams) => { + const { mapId, filters, query, refreshInterval, timeRange, initialLayers, hash } = params; + const useHash = hash ?? this.deps.useHash; + const appState: { + query?: Query; + filters?: Filter[]; + vis?: unknown; + } = {}; + const queryState: QueryState = {}; + + if (query) appState.query = query; + if (filters && filters.length) + appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (timeRange) queryState.time = timeRange; + if (filters && filters.length) + queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let path = `/map#/${mapId || ''}`; + path = setStateToKbnUrl('_g', queryState, { useHash }, path); + path = setStateToKbnUrl('_a', appState, { useHash }, path); + + if (initialLayers && initialLayers.length) { + const risonEncodedInitialLayers = ((rison as unknown) as { + encode_array: ( + initialLayers: (LayerDescriptor[] & SerializableState) | undefined + ) => string; + }).encode_array(initialLayers); + path = `${path}&${INITIAL_LAYERS_KEY}=${encodeURIComponent(risonEncodedInitialLayers)}`; + } + + return { + app: APP_ID, + path, + state: {}, + }; + }; +} + +export interface MapsAppTileMapLocatorParams extends SerializableState { + label: string; + mapType: string; + colorSchema: string; + indexPatternId?: string; + geoFieldName?: string; + metricAgg: string; + metricFieldName?: string; + timeRange?: TimeRange; + filters?: Filter[]; + query?: Query; + hash?: boolean; +} + +export type MapsAppTileMapLocator = LocatorPublic; + +export const MAPS_APP_TILE_MAP_LOCATOR = 'MAPS_APP_TILE_MAP_LOCATOR' as const; + +export interface MapsAppTileMapLocatorDependencies { + locator: MapsAppLocator; +} + +export class MapsAppTileMapLocatorDefinition + implements LocatorDefinition { + public readonly id = MAPS_APP_TILE_MAP_LOCATOR; + + constructor(protected readonly deps: MapsAppTileMapLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppTileMapLocatorParams) => { + const { + label, + mapType, + colorSchema, + indexPatternId, + geoFieldName, + metricAgg, + metricFieldName, + filters, + query, + timeRange, + hash = true, + } = params; + const mapModules = await lazyLoadMapModules(); + const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableState; + const tileMapLayerDescriptor = mapModules.createTileMapLayerDescriptor({ + label, + mapType, + colorSchema, + indexPatternId, + geoFieldName, + metricAgg, + metricFieldName, + }); + + if (tileMapLayerDescriptor) { + initialLayers.push(tileMapLayerDescriptor); + } + + return await this.deps.locator.getLocation({ + initialLayers, + filters, + query, + timeRange, + hash, + }); + }; +} + +export interface MapsAppRegionMapLocatorParams extends SerializableState { + label: string; + emsLayerId?: string; + leftFieldName?: string; + termsFieldName?: string; + termsSize?: number; + colorSchema: string; + indexPatternId?: string; + indexPatternTitle?: string; + metricAgg: string; + metricFieldName?: string; + timeRange?: TimeRange; + filters?: Filter[]; + query?: Query; + hash?: boolean; +} + +export type MapsAppRegionMapLocator = LocatorPublic; + +export const MAPS_APP_REGION_MAP_LOCATOR = 'MAPS_APP_REGION_MAP_LOCATOR' as const; + +export interface MapsAppRegionMapLocatorDependencies { + locator: MapsAppLocator; +} + +export class MapsAppRegionMapLocatorDefinition + implements LocatorDefinition { + public readonly id = MAPS_APP_REGION_MAP_LOCATOR; + + constructor(protected readonly deps: MapsAppRegionMapLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppRegionMapLocatorParams) => { + const { + label, + emsLayerId, + leftFieldName, + termsFieldName, + termsSize, + colorSchema, + indexPatternId, + indexPatternTitle, + metricAgg, + metricFieldName, + filters, + query, + timeRange, + hash = true, + } = params; + const mapModules = await lazyLoadMapModules(); + const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableState; + const regionMapLayerDescriptor = mapModules.createRegionMapLayerDescriptor({ + label, + emsLayerId, + leftFieldName, + termsFieldName, + termsSize, + colorSchema, + indexPatternId, + indexPatternTitle, + metricAgg, + metricFieldName, + }); + if (regionMapLayerDescriptor) { + initialLayers.push(regionMapLayerDescriptor); + } + + return await this.deps.locator.getLocation({ + initialLayers, + filters, + query, + timeRange, + hash, + }); + }; +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 740112124a251..b526e7b24d90f 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -36,11 +36,6 @@ import type { } from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; -import { - createMapsUrlGenerator, - createRegionMapUrlGenerator, - createTileMapUrlGenerator, -} from './url_generator'; import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action'; import { filterByMapExtentAction } from './trigger_actions/filter_by_map_extent_action'; import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory'; @@ -71,6 +66,11 @@ import { import { EMSSettings } from '../common/ems_settings'; import type { SavedObjectTaggingPluginStart } from '../../saved_objects_tagging/public'; import type { ChartsPluginStart } from '../../../../src/plugins/charts/public'; +import { + MapsAppLocatorDefinition, + MapsAppRegionMapLocatorDefinition, + MapsAppTileMapLocatorDefinition, +} from './locators'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -133,17 +133,21 @@ export class MapsPlugin const emsSettings = new EMSSettings(plugins.mapsEms.config, getIsEnterprisePlus); setEMSSettings(emsSettings); - // register url generators - const getStartServices = async () => { - const [coreStart] = await core.getStartServices(); - return { - appBasePath: coreStart.application.getUrlForApp('maps'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - }; - }; - plugins.share.urlGenerators.registerUrlGenerator(createMapsUrlGenerator(getStartServices)); - plugins.share.urlGenerators.registerUrlGenerator(createTileMapUrlGenerator(getStartServices)); - plugins.share.urlGenerators.registerUrlGenerator(createRegionMapUrlGenerator(getStartServices)); + const locator = plugins.share.url.locators.create( + new MapsAppLocatorDefinition({ + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); + plugins.share.url.locators.create( + new MapsAppTileMapLocatorDefinition({ + locator, + }) + ); + plugins.share.url.locators.create( + new MapsAppRegionMapLocatorDefinition({ + locator, + }) + ); plugins.inspector.registerView(MapView); if (plugins.home) { diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts index acdc5164cb17d..c6daee554c9fc 100644 --- a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts +++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts @@ -7,6 +7,7 @@ import uuid from 'uuid/v4'; import { i18n } from '@kbn/i18n'; +import { SerializableState } from 'src/plugins/kibana_utils/common'; import { createAction, ACTION_VISUALIZE_GEO_FIELD, @@ -17,10 +18,11 @@ import { getIndexPatternService, getData, getShareService, - getNavigateToApp, + getCore, } from '../kibana_services'; -import { MAPS_APP_URL_GENERATOR, MapsUrlGeneratorState } from '../url_generator'; -import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES, APP_ID, MAP_PATH } from '../../common/constants'; +import { MapsAppLocator, MAPS_APP_LOCATOR } from '../locators'; +import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES } from '../../common/constants'; +import { LayerDescriptor } from '../../common/descriptor_types'; export const visualizeGeoFieldAction = createAction({ id: ACTION_VISUALIZE_GEO_FIELD, @@ -31,15 +33,19 @@ export const visualizeGeoFieldAction = createAction({ }), isCompatible: async () => !!getVisualizeCapabilities().show, getHref: async (context) => { - const url = await getMapsLink(context); - return url; + const { app, path } = await getMapsLink(context); + + return getCore().application.getUrlForApp(app, { + path, + absolute: false, + }); }, execute: async (context) => { - const url = await getMapsLink(context); - const hash = url.split('#')[1]; + const { app, path, state } = await getMapsLink(context); - getNavigateToApp()(APP_ID, { - path: `${MAP_PATH}/#${hash}`, + getCore().application.navigateToApp(app, { + path, + state, }); }, }); @@ -68,12 +74,13 @@ const getMapsLink = async (context: VisualizeFieldContext) => { }, ]; - const generator = getShareService().urlGenerators.getUrlGenerator(MAPS_APP_URL_GENERATOR); - const urlState: MapsUrlGeneratorState = { + const locator = getShareService().url.locators.get(MAPS_APP_LOCATOR) as MapsAppLocator; + const location = await locator.getLocation({ filters: getData().query.filterManager.getFilters(), query: getData().query.queryString.getQuery(), - initialLayers, + initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableState, timeRange: getData().query.timefilter.timefilter.getTime(), - }; - return generator.createUrl(urlState); + }); + + return location; }; diff --git a/x-pack/plugins/maps/public/url_generator.ts b/x-pack/plugins/maps/public/url_generator.ts deleted file mode 100644 index 9f28b388c4756..0000000000000 --- a/x-pack/plugins/maps/public/url_generator.ts +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import rison from 'rison-node'; -import type { - TimeRange, - Filter, - Query, - QueryState, - RefreshInterval, -} from '../../../../src/plugins/data/public'; -import { esFilters } from '../../../../src/plugins/data/public'; -import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; -import type { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; -import type { LayerDescriptor } from '../common/descriptor_types'; -import { INITIAL_LAYERS_KEY } from '../common/constants'; -import { lazyLoadMapModules } from './lazy_load_bundle'; - -const STATE_STORAGE_KEY = '_a'; -const GLOBAL_STATE_STORAGE_KEY = '_g'; - -export const MAPS_APP_URL_GENERATOR = 'MAPS_APP_URL_GENERATOR'; -export const MAPS_APP_TILE_MAP_URL_GENERATOR = 'MAPS_APP_TILE_MAP_URL_GENERATOR'; -export const MAPS_APP_REGION_MAP_URL_GENERATOR = 'MAPS_APP_REGION_MAP_URL_GENERATOR'; - -export interface MapsUrlGeneratorState { - /** - * If given, it will load the given map else will load the create a new map page. - */ - mapId?: string; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - - /** - * Optionally set the initial Layers. - */ - initialLayers?: LayerDescriptor[]; - - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - - /** - * Optionally apply filers. NOTE: if given and used in conjunction with `mapId`, and the - * saved map has filters saved with it, this will _replace_ those filters. - */ - filters?: Filter[]; - /** - * Optionally set a query. NOTE: if given and used in conjunction with `mapId`, and the - * saved map has a query saved with it, this will _replace_ that query. - */ - query?: Query; - /** - * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines - * whether to hash the data in the url to avoid url length issues. - */ - hash?: boolean; -} - -type GetStartServices = () => Promise<{ - appBasePath: string; - useHashedUrl: boolean; -}>; - -async function createMapUrl({ - getStartServices, - mapId, - filters, - query, - refreshInterval, - timeRange, - initialLayers, - hash, -}: MapsUrlGeneratorState & { getStartServices: GetStartServices }): Promise { - const startServices = await getStartServices(); - const useHash = hash ?? startServices.useHashedUrl; - const appBasePath = startServices.appBasePath; - - const appState: { - query?: Query; - filters?: Filter[]; - vis?: unknown; - } = {}; - const queryState: QueryState = {}; - - if (query) appState.query = query; - if (filters && filters.length) - appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); - - if (timeRange) queryState.time = timeRange; - if (filters && filters.length) - queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); - if (refreshInterval) queryState.refreshInterval = refreshInterval; - - let url = `${appBasePath}/map#/${mapId || ''}`; - url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url); - url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url); - - if (initialLayers && initialLayers.length) { - // @ts-ignore - const risonEncodedInitialLayers = rison.encode_array(initialLayers); - url = `${url}&${INITIAL_LAYERS_KEY}=${encodeURIComponent(risonEncodedInitialLayers)}`; - } - - return url; -} - -export const createMapsUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_URL_GENERATOR, - createUrl: async (mapsUrlGeneratorState: MapsUrlGeneratorState): Promise => { - return createMapUrl({ ...mapsUrlGeneratorState, getStartServices }); - }, -}); - -export const createTileMapUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_TILE_MAP_URL_GENERATOR, - createUrl: async ({ - label, - mapType, - colorSchema, - indexPatternId, - geoFieldName, - metricAgg, - metricFieldName, - filters, - query, - timeRange, - hash, - }: { - label: string; - mapType: string; - colorSchema: string; - indexPatternId?: string; - geoFieldName?: string; - metricAgg: string; - metricFieldName?: string; - timeRange?: TimeRange; - filters?: Filter[]; - query?: Query; - hash?: boolean; - }): Promise => { - const mapModules = await lazyLoadMapModules(); - const initialLayers = []; - const tileMapLayerDescriptor = mapModules.createTileMapLayerDescriptor({ - label, - mapType, - colorSchema, - indexPatternId, - geoFieldName, - metricAgg, - metricFieldName, - }); - if (tileMapLayerDescriptor) { - initialLayers.push(tileMapLayerDescriptor); - } - - return createMapUrl({ - initialLayers, - filters, - query, - timeRange, - hash: true, - getStartServices, - }); - }, -}); - -export const createRegionMapUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_REGION_MAP_URL_GENERATOR, - createUrl: async ({ - label, - emsLayerId, - leftFieldName, - termsFieldName, - termsSize, - colorSchema, - indexPatternId, - indexPatternTitle, - metricAgg, - metricFieldName, - filters, - query, - timeRange, - hash, - }: { - label: string; - emsLayerId?: string; - leftFieldName?: string; - termsFieldName?: string; - termsSize?: number; - colorSchema: string; - indexPatternId?: string; - indexPatternTitle?: string; - metricAgg: string; - metricFieldName?: string; - timeRange?: TimeRange; - filters?: Filter[]; - query?: Query; - hash?: boolean; - }): Promise => { - const mapModules = await lazyLoadMapModules(); - const initialLayers = []; - const regionMapLayerDescriptor = mapModules.createRegionMapLayerDescriptor({ - label, - emsLayerId, - leftFieldName, - termsFieldName, - termsSize, - colorSchema, - indexPatternId, - indexPatternTitle, - metricAgg, - metricFieldName, - }); - if (regionMapLayerDescriptor) { - initialLayers.push(regionMapLayerDescriptor); - } - - return createMapUrl({ - initialLayers, - filters, - query, - timeRange, - hash: true, - getStartServices, - }); - }, -}); From baf2de5415c6c7ad5a29a95ff54f8376896d1b81 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 25 Jun 2021 07:58:03 +0200 Subject: [PATCH 23/40] Dashboard locator (#102854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add dashboard locator * feat: 🎸 expose dashboard locator from dashboard plugin * Use dashboard locator in dashboard drilldown * Add tests for dashboard locator * Fix dashboard drilldown tests after refactor * Deprecate dashboard URL generator * Fix TypeScript errors in exmaple plugin * Use correct type for dashboard locator * refactor: 💡 change "route" attribute to "path" * chore: 🤖 remove unused bundle Co-authored-by: Vadim Kibana Co-authored-by: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/dashboard/public/index.ts | 3 + src/plugins/dashboard/public/locator.test.ts | 323 ++++++++++++++++++ src/plugins/dashboard/public/locator.ts | 160 +++++++++ src/plugins/dashboard/public/plugin.tsx | 36 +- src/plugins/dashboard/public/url_generator.ts | 6 + .../ui_actions_enhanced_examples/kibana.json | 3 +- .../app1_to_dashboard_drilldown.ts | 9 +- .../app2_to_dashboard_drilldown.ts | 9 +- .../abstract_dashboard_drilldown.tsx | 28 +- ...embeddable_to_dashboard_drilldown.test.tsx | 37 +- .../embeddable_to_dashboard_drilldown.tsx | 23 +- 11 files changed, 578 insertions(+), 59 deletions(-) create mode 100644 src/plugins/dashboard/public/locator.test.ts create mode 100644 src/plugins/dashboard/public/locator.ts diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index c584b44286e07..ff7708689c221 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -22,11 +22,14 @@ export { DashboardUrlGenerator, DashboardFeatureFlagConfig, } from './plugin'; + export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator, DashboardUrlGeneratorState, } from './url_generator'; +export { DashboardAppLocator, DashboardAppLocatorParams } from './locator'; + export { DashboardSavedObject } from './saved_dashboards'; export { SavedDashboardPanel, DashboardContainerInput } from './types'; diff --git a/src/plugins/dashboard/public/locator.test.ts b/src/plugins/dashboard/public/locator.test.ts new file mode 100644 index 0000000000000..0b647ac00ce31 --- /dev/null +++ b/src/plugins/dashboard/public/locator.test.ts @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardAppLocatorDefinition } from './locator'; +import { hashedItemStore } from '../../kibana_utils/public'; +import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; +import { esFilters } from '../../data/public'; + +describe('dashboard locator', () => { + beforeEach(() => { + // @ts-ignore + hashedItemStore.storage = mockStorage; + }); + + test('creates a link to a saved dashboard', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({}); + + expect(location).toMatchObject({ + app: 'dashboards', + path: '#/create?_a=()&_g=()', + state: {}, + }); + }); + + test('creates a link with global time range set up', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: '#/create?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))', + state: {}, + }); + }); + + test('creates a link with filters, time range, refresh interval and query to a saved object', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + refreshInterval: { pause: false, value: 300 }, + dashboardId: '123', + filters: [ + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'hi' }, + }, + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'hi' }, + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + }, + ], + query: { query: 'bye', language: 'kuery' }, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/view/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))`, + state: {}, + }); + }); + + test('searchSessionId', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + refreshInterval: { pause: false, value: 300 }, + dashboardId: '123', + filters: [], + query: { query: 'bye', language: 'kuery' }, + searchSessionId: '__sessionSearchId__', + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/view/123?_a=(filters:!(),query:(language:kuery,query:bye))&_g=(filters:!(),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&searchSessionId=__sessionSearchId__`, + state: {}, + }); + }); + + test('savedQuery', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + savedQuery: '__savedQueryId__', + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/create?_a=(savedQuery:__savedQueryId__)&_g=()`, + state: {}, + }); + expect(location.path).toContain('__savedQueryId__'); + }); + + test('panels', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + panels: [{ fakePanelContent: 'fakePanelContent' }] as any, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/create?_a=(panels:!((fakePanelContent:fakePanelContent)))&_g=()`, + state: {}, + }); + }); + + test('if no useHash setting is given, uses the one was start services', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: true, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + + expect(location.path.indexOf('relative')).toBe(-1); + }); + + test('can override a false useHash ui setting', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + useHash: true, + }); + + expect(location.path.indexOf('relative')).toBe(-1); + }); + + test('can override a true useHash ui setting', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: true, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + useHash: false, + }); + + expect(location.path.indexOf('relative')).toBeGreaterThan(1); + }); + + describe('preserving saved filters', () => { + const savedFilter1 = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'savedfilter1' }, + }; + + const savedFilter2 = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'savedfilter2' }, + }; + + const appliedFilter = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'appliedfilter' }, + }; + + test('attaches filters from destination dashboard', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + return dashboardId === 'dashboard1' + ? [savedFilter1] + : dashboardId === 'dashboard2' + ? [savedFilter2] + : []; + }, + }); + + const location1 = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + }); + + expect(location1.path).toEqual(expect.stringContaining('query:savedfilter1')); + expect(location1.path).toEqual(expect.stringContaining('query:appliedfilter')); + + const location2 = await definition.getLocation({ + dashboardId: 'dashboard2', + filters: [appliedFilter], + }); + + expect(location2.path).toEqual(expect.stringContaining('query:savedfilter2')); + expect(location2.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + + test("doesn't fail if can't retrieve filters from destination dashboard", async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + throw new Error('Not found'); + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + + test('can enforce empty filters', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [], + preserveSavedFilters: false, + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).not.toEqual(expect.stringContaining('query:appliedfilter')); + expect(location.path).toMatchInlineSnapshot( + `"#/view/dashboard1?_a=(filters:!())&_g=(filters:!())"` + ); + }); + + test('no filters in result url if no filters applied', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + }); + + expect(location.path).not.toEqual(expect.stringContaining('filters')); + expect(location.path).toMatchInlineSnapshot(`"#/view/dashboard1?_a=()&_g=()"`); + }); + + test('can turn off preserving filters', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + preserveSavedFilters: false, + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + }); +}); diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts new file mode 100644 index 0000000000000..e154351819ee9 --- /dev/null +++ b/src/plugins/dashboard/public/locator.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SerializableState } from 'src/plugins/kibana_utils/common'; +import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public'; +import type { LocatorDefinition, LocatorPublic } from '../../share/public'; +import type { SavedDashboardPanel } from '../common/types'; +import { esFilters } from '../../data/public'; +import { setStateToKbnUrl } from '../../kibana_utils/public'; +import { ViewMode } from '../../embeddable/public'; +import { DashboardConstants } from './dashboard_constants'; + +const cleanEmptyKeys = (stateObj: Record) => { + Object.keys(stateObj).forEach((key) => { + if (stateObj[key] === undefined) { + delete stateObj[key]; + } + }); + return stateObj; +}; + +export const DASHBOARD_APP_LOCATOR = 'DASHBOARD_APP_LOCATOR'; + +export interface DashboardAppLocatorParams extends SerializableState { + /** + * If given, the dashboard saved object with this id will be loaded. If not given, + * a new, unsaved dashboard will be loaded up. + */ + dashboardId?: string; + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval & SerializableState; + + /** + * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has filters saved with it, this will _replace_ those filters. + */ + filters?: Filter[]; + /** + * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has a query saved with it, this will _replace_ that query. + */ + query?: Query; + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + useHash?: boolean; + + /** + * When `true` filters from saved filters from destination dashboard as merged with applied filters + * When `false` applied filters take precedence and override saved filters + * + * true is default + */ + preserveSavedFilters?: boolean; + + /** + * View mode of the dashboard. + */ + viewMode?: ViewMode; + + /** + * Search search session ID to restore. + * (Background search) + */ + searchSessionId?: string; + + /** + * List of dashboard panels + */ + panels?: SavedDashboardPanel[] & SerializableState; + + /** + * Saved query ID + */ + savedQuery?: string; +} + +export type DashboardAppLocator = LocatorPublic; + +export interface DashboardAppLocatorDependencies { + useHashedUrl: boolean; + getDashboardFilterFields: (dashboardId: string) => Promise; +} + +export class DashboardAppLocatorDefinition implements LocatorDefinition { + public readonly id = DASHBOARD_APP_LOCATOR; + + constructor(protected readonly deps: DashboardAppLocatorDependencies) {} + + public readonly getLocation = async (params: DashboardAppLocatorParams) => { + const useHash = params.useHash ?? this.deps.useHashedUrl; + const hash = params.dashboardId ? `view/${params.dashboardId}` : `create`; + + const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise => { + if (params.preserveSavedFilters === false) return []; + if (!params.dashboardId) return []; + try { + return await this.deps.getDashboardFilterFields(params.dashboardId); + } catch (e) { + // In case dashboard is missing, build the url without those filters. + // The Dashboard app will handle redirect to landing page with a toast message. + return []; + } + }; + + // leave filters `undefined` if no filters was applied + // in this case dashboard will restore saved filters on its own + const filters = params.filters && [ + ...(await getSavedFiltersFromDestinationDashboardIfNeeded()), + ...params.filters, + ]; + + let path = setStateToKbnUrl( + '_a', + cleanEmptyKeys({ + query: params.query, + filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), + viewMode: params.viewMode, + panels: params.panels, + savedQuery: params.savedQuery, + }), + { useHash }, + `#/${hash}` + ); + + path = setStateToKbnUrl( + '_g', + cleanEmptyKeys({ + time: params.timeRange, + filters: filters?.filter((f) => esFilters.isFilterPinned(f)), + refreshInterval: params.refreshInterval, + }), + { useHash }, + path + ); + + if (params.searchSessionId) { + path = `${path}&${DashboardConstants.SEARCH_SESSION_ID}=${params.searchSessionId}`; + } + + return { + app: DashboardConstants.DASHBOARDS_ID, + path, + state: {}, + }; + }; +} diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index b5d6eda71ca4a..53a8e90a8c35c 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -72,6 +72,7 @@ import { DASHBOARD_APP_URL_GENERATOR, DashboardUrlGeneratorState, } from './url_generator'; +import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; @@ -121,14 +122,25 @@ export interface DashboardStartDependencies { visualizations: VisualizationsStart; } -export type DashboardSetup = void; +export interface DashboardSetup { + locator?: DashboardAppLocator; +} export interface DashboardStart { getSavedDashboardLoader: () => SavedObjectLoader; getDashboardContainerByValueRenderer: () => ReturnType< typeof createDashboardContainerByValueRenderer >; + /** + * @deprecated Use dashboard locator instead. Dashboard locator is available + * under `.locator` key. This dashboard URL generator will be removed soon. + * + * ```ts + * plugins.dashboard.locator.getLocation({ ... }); + * ``` + */ dashboardUrlGenerator?: DashboardUrlGenerator; + locator?: DashboardAppLocator; dashboardFeatureFlagConfig: DashboardFeatureFlagConfig; } @@ -142,7 +154,11 @@ export class DashboardPlugin private currentHistory: ScopedHistory | undefined = undefined; private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig; + /** + * @deprecated Use locator instead. + */ private dashboardUrlGenerator?: DashboardUrlGenerator; + private locator?: DashboardAppLocator; public setup( core: CoreSetup, @@ -205,6 +221,19 @@ export class DashboardPlugin }; }; + if (share) { + this.locator = share.url.locators.create( + new DashboardAppLocatorDefinition({ + useHashedUrl: core.uiSettings.get('state:storeInSessionStorage'), + getDashboardFilterFields: async (dashboardId: string) => { + const [, , selfStart] = await core.getStartServices(); + const dashboard = await selfStart.getSavedDashboardLoader().get(dashboardId); + return dashboard?.searchSource?.getField('filter') ?? []; + }, + }) + ); + } + const { appMounted, appUnMounted, @@ -333,6 +362,10 @@ export class DashboardPlugin order: 100, }); } + + return { + locator: this.locator, + }; } public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart { @@ -417,6 +450,7 @@ export class DashboardPlugin }); }, dashboardUrlGenerator: this.dashboardUrlGenerator, + locator: this.locator, dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!, }; } diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index 58036ef70fa4a..5c0cd32ee5a16 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -26,6 +26,9 @@ export const GLOBAL_STATE_STORAGE_KEY = '_g'; export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR'; +/** + * @deprecated Use dashboard locator instead. + */ export interface DashboardUrlGeneratorState { /** * If given, the dashboard saved object with this id will be loaded. If not given, @@ -88,6 +91,9 @@ export interface DashboardUrlGeneratorState { savedQuery?: string; } +/** + * @deprecated Use dashboard locator instead. + */ export const createDashboardUrlGenerator = ( getStartServices: () => Promise<{ appBasePath: string; diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json index 4f5ac8519fe5b..59a0926118962 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json @@ -19,7 +19,6 @@ "dashboardEnhanced", "embeddable", "kibanaUtils", - "kibanaReact", - "share" + "kibanaReact" ] } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app1_to_dashboard_drilldown/app1_to_dashboard_drilldown.ts b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app1_to_dashboard_drilldown/app1_to_dashboard_drilldown.ts index 3efe2fed9a78a..1bb911530dc50 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app1_to_dashboard_drilldown/app1_to_dashboard_drilldown.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app1_to_dashboard_drilldown/app1_to_dashboard_drilldown.ts @@ -10,7 +10,7 @@ import { DashboardEnhancedAbstractDashboardDrilldownConfig as Config, } from '../../../../../plugins/dashboard_enhanced/public'; import { SAMPLE_APP1_CLICK_TRIGGER, SampleApp1ClickContext } from '../../triggers'; -import { KibanaURL } from '../../../../../../src/plugins/share/public'; +import { KibanaLocation } from '../../../../../../src/plugins/share/public'; export const APP1_TO_DASHBOARD_DRILLDOWN = 'APP1_TO_DASHBOARD_DRILLDOWN'; @@ -21,12 +21,11 @@ export class App1ToDashboardDrilldown extends AbstractDashboardDrilldown [SAMPLE_APP1_CLICK_TRIGGER]; - protected async getURL(config: Config, context: Context): Promise { - const path = await this.urlGenerator.createUrl({ + protected async getLocation(config: Config, context: Context): Promise { + const location = await this.locator.getLocation({ dashboardId: config.dashboardId, }); - const url = new KibanaURL(path); - return url; + return location; } } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app2_to_dashboard_drilldown/app2_to_dashboard_drilldown.ts b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app2_to_dashboard_drilldown/app2_to_dashboard_drilldown.ts index 7245e37414686..daac998872c14 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app2_to_dashboard_drilldown/app2_to_dashboard_drilldown.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/app2_to_dashboard_drilldown/app2_to_dashboard_drilldown.ts @@ -10,7 +10,7 @@ import { DashboardEnhancedAbstractDashboardDrilldownConfig as Config, } from '../../../../../plugins/dashboard_enhanced/public'; import { SAMPLE_APP2_CLICK_TRIGGER, SampleApp2ClickContext } from '../../triggers'; -import { KibanaURL } from '../../../../../../src/plugins/share/public'; +import { KibanaLocation } from '../../../../../../src/plugins/share/public'; export const APP2_TO_DASHBOARD_DRILLDOWN = 'APP2_TO_DASHBOARD_DRILLDOWN'; @@ -21,12 +21,11 @@ export class App2ToDashboardDrilldown extends AbstractDashboardDrilldown [SAMPLE_APP2_CLICK_TRIGGER]; - protected async getURL(config: Config, context: Context): Promise { - const path = await this.urlGenerator.createUrl({ + protected async getLocation(config: Config, context: Context): Promise { + const location = await this.locator.getLocation({ dashboardId: config.dashboardId, }); - const url = new KibanaURL(path); - return url; + return location; } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx index 1b1fecb2bcaf7..6f02946596a87 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/abstract_dashboard_drilldown.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { KibanaLocation } from 'src/plugins/share/public'; import React from 'react'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { DashboardStart } from 'src/plugins/dashboard/public'; @@ -20,7 +21,6 @@ import { CollectConfigProps, StartServicesGetter, } from '../../../../../../../src/plugins/kibana_utils/public'; -import { KibanaURL } from '../../../../../../../src/plugins/share/public'; import { Config } from './types'; export interface Params { @@ -39,7 +39,7 @@ export abstract class AbstractDashboardDrilldown string[]; - protected abstract getURL(config: Config, context: Context): Promise; + protected abstract getLocation(config: Config, context: Context): Promise; public readonly order = 100; @@ -65,19 +65,25 @@ export abstract class AbstractDashboardDrilldown => { - const url = await this.getURL(config, context); - return url.path; + const { app, path } = await this.getLocation(config, context); + const url = await this.params.start().core.application.getUrlForApp(app, { + path, + absolute: true, + }); + return url; }; public readonly execute = async (config: Config, context: Context) => { - const url = await this.getURL(config, context); - await this.params.start().core.application.navigateToApp(url.appName, { path: url.appPath }); + const { app, path, state } = await this.getLocation(config, context); + await this.params.start().core.application.navigateToApp(app, { + path, + state, + }); }; - protected get urlGenerator() { - const urlGenerator = this.params.start().plugins.dashboard.dashboardUrlGenerator; - if (!urlGenerator) - throw new Error('Dashboard URL generator is required for dashboard drilldown.'); - return urlGenerator; + protected get locator() { + const locator = this.params.start().plugins.dashboard.locator; + if (!locator) throw new Error('Dashboard locator is required for dashboard drilldown.'); + return locator; } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx index cd82e23544ad0..a817d9f65c916 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.test.tsx @@ -7,7 +7,7 @@ import { EmbeddableToDashboardDrilldown } from './embeddable_to_dashboard_drilldown'; import { AbstractDashboardDrilldownConfig as Config } from '../abstract_dashboard_drilldown'; -import { coreMock, savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks'; +import { savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks'; import { Filter, FilterStateStore, @@ -19,10 +19,11 @@ import { ApplyGlobalFilterActionContext, esFilters, } from '../../../../../../../src/plugins/data/public'; -import { createDashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public/url_generator'; -import { UrlGeneratorsService } from '../../../../../../../src/plugins/share/public/url_generators'; +import { + DashboardAppLocatorDefinition, + DashboardAppLocatorParams, +} from '../../../../../../../src/plugins/dashboard/public/locator'; import { StartDependencies } from '../../../plugin'; -import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public/core'; import { EnhancedEmbeddableContext } from '../../../../../embeddable_enhanced/public'; @@ -74,13 +75,6 @@ test('inject/extract are defined', () => { }); describe('.execute() & getHref', () => { - /** - * A convenience test setup helper - * Beware: `dataPluginMock.createStartContract().actions` and extracting filters from event is mocked! - * The url generation is not mocked and uses real implementation - * So this tests are mostly focused on making sure the filters returned from `dataPluginMock.createStartContract().actions` helpers - * end up in resulting navigation path - */ async function setupTestBed( config: Partial, embeddableInput: { filters?: Filter[]; timeRange?: TimeRange; query?: Query }, @@ -90,7 +84,10 @@ describe('.execute() & getHref', () => { const navigateToApp = jest.fn(); const getUrlForApp = jest.fn((app, opt) => `${app}/${opt.path}`); const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; - + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async () => [], + }); const drilldown = new EmbeddableToDashboardDrilldown({ start: ((() => ({ core: { @@ -105,17 +102,11 @@ describe('.execute() & getHref', () => { plugins: { uiActionsEnhanced: {}, dashboard: { - dashboardUrlGenerator: new UrlGeneratorsService() - .setup(coreMock.createSetup()) - .registerUrlGenerator( - createDashboardUrlGenerator(() => - Promise.resolve({ - appBasePath: 'xyz/app/dashboards', - useHashedUrl: false, - savedDashboardLoader: ({} as unknown) as SavedObjectLoader, - }) - ) - ), + locator: { + getLocation: async (params: DashboardAppLocatorParams) => { + return await definition.getLocation(params); + }, + }, }, }, self: {}, diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx index 7b55244985981..97355d9eb435e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { DashboardUrlGeneratorState } from '../../../../../../../src/plugins/dashboard/public'; +import type { KibanaLocation } from 'src/plugins/share/public'; +import { DashboardAppLocatorParams } from '../../../../../../../src/plugins/dashboard/public'; import { ApplyGlobalFilterActionContext, APPLY_FILTER_TRIGGER, @@ -23,7 +24,6 @@ import { AbstractDashboardDrilldownParams, AbstractDashboardDrilldownConfig as Config, } from '../abstract_dashboard_drilldown'; -import { KibanaURL } from '../../../../../../../src/plugins/share/public'; import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; import { createExtract, createInject } from '../../../../common'; import { EnhancedEmbeddableContext } from '../../../../../embeddable_enhanced/public'; @@ -49,26 +49,26 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown [APPLY_FILTER_TRIGGER]; - protected async getURL(config: Config, context: Context): Promise { - const state: DashboardUrlGeneratorState = { + protected async getLocation(config: Config, context: Context): Promise { + const params: DashboardAppLocatorParams = { dashboardId: config.dashboardId, }; if (context.embeddable) { const embeddable = context.embeddable as IEmbeddable; const input = embeddable.getInput(); - if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query; + if (isQuery(input.query) && config.useCurrentFilters) params.query = input.query; // if useCurrentDashboardDataRange is enabled, then preserve current time range // if undefined is passed, then destination dashboard will figure out time range itself // for brush event this time range would be overwritten if (isTimeRange(input.timeRange) && config.useCurrentDateRange) - state.timeRange = input.timeRange; + params.timeRange = input.timeRange; // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) // otherwise preserve only pinned if (isFilters(input.filters)) - state.filters = config.useCurrentFilters + params.filters = config.useCurrentFilters ? input.filters : input.filters?.filter((f) => esFilters.isFilterPinned(f)); } @@ -79,17 +79,16 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown Date: Fri, 25 Jun 2021 11:03:46 +0300 Subject: [PATCH 24/40] [Osquery] Return proper indices permissions for osquery_manager package (#103363) --- ...kage_policies_to_agent_permissions.test.ts | 104 ++++++++++++++++++ .../package_policies_to_agent_permissions.ts | 7 ++ 2 files changed, 111 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts index 39759a6fc9e9c..a84118cdf1bfa 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts @@ -274,6 +274,110 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { }, }); }); + + it('Returns the dataset for osquery_manager package', async () => { + getPackageInfoMock.mockResolvedValueOnce({ + format_version: '1.0.0', + name: 'osquery_manager', + title: 'Osquery Manager', + version: '0.3.0', + license: 'basic', + description: + 'Centrally manage osquery deployments, run live queries, and schedule recurring queries', + type: 'integration', + release: 'beta', + categories: ['security', 'os_system', 'config_management'], + icons: [ + { + src: '/img/logo_osquery.svg', + title: 'logo osquery', + size: '32x32', + type: 'image/svg+xml', + }, + ], + owner: { github: 'elastic/integrations' }, + readme: '/package/osquery_manager/0.3.0/docs/README.md', + data_streams: [ + { + dataset: 'osquery_manager.result', + package: 'osquery_manager', + ingest_pipeline: 'default', + path: 'result', + streams: [], + title: 'Osquery Manager queries', + type: 'logs', + release: 'experimental', + }, + ], + latestVersion: '0.3.0', + removable: true, + notice: undefined, + status: 'not_installed', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + index_pattern: [], + map: [], + lens: [], + security_rule: [], + ml_module: [], + }, + elasticsearch: { + component_template: [], + ingest_pipeline: [], + ilm_policy: [], + transform: [], + index_template: [], + data_stream_ilm_policy: [], + }, + }, + }); + + const packagePolicies: PackagePolicy[] = [ + { + id: '12345', + name: 'test-policy', + namespace: 'test', + enabled: true, + package: { name: 'osquery_manager', version: '0.0.0', title: 'Test Package' }, + inputs: [ + { + type: 'osquery_manager', + enabled: true, + streams: [ + { + id: 'test-logs', + enabled: true, + data_stream: { type: 'logs', dataset: 'some-logs' }, + compiled_stream: { data_stream: { dataset: 'compiled' } }, + }, + ], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + output_id: '', + }, + ]; + + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, packagePolicies); + expect(permissions).toMatchObject({ + 'test-policy': { + indices: [ + { + names: ['logs-osquery_manager.result-test'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }); + }); }); describe('getDataStreamPermissions()', () => { diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts index bd73b88e7c893..07ad892adc653 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts @@ -73,6 +73,13 @@ export async function storedPackagePoliciesToAgentPermissions( dataStreamsForPermissions = pkg.data_streams; break; + case 'osquery_manager': + // - Osquery manager doesn't store the `data_stream` metadata in + // `packagePolicy.inputs`, so we will use _all_ data_streams from + // the package. + dataStreamsForPermissions = pkg.data_streams; + break; + default: // - Normal packages store some of the `data_stream` metadata in // `packagePolicy.inputs[].streams[].data_stream` From dfc70bdfd19992edda1a8ab832c2224706172a34 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 25 Jun 2021 14:59:36 +0200 Subject: [PATCH 25/40] [Lens] Enable actions on Lens Embeddable (#102038) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...kibana-plugin-plugins-embeddable-public.md | 1 + ...-embeddable-public.useembeddablefactory.md | 22 +++ src/plugins/embeddable/public/index.ts | 1 + .../embeddables/embeddable_renderer.test.tsx | 27 ++- .../lib/embeddables/embeddable_renderer.tsx | 154 ++++++++++-------- .../public/lib/embeddables/index.ts | 6 +- .../lib/panel/embeddable_panel.test.tsx | 37 +++++ .../public/lib/panel/embeddable_panel.tsx | 83 ++++++++-- .../lib/panel/panel_header/panel_header.tsx | 6 +- src/plugins/embeddable/public/public.api.md | 5 + .../embedded_lens_example/kibana.json | 1 + .../embedded_lens_example/public/app.tsx | 3 + x-pack/plugins/lens/kibana.json | 1 + .../embeddable/embeddable_component.tsx | 76 ++++++++- x-pack/plugins/lens/public/index.ts | 2 +- x-pack/plugins/lens/public/plugin.ts | 4 +- .../public/custom_time_range_action.tsx | 4 +- 17 files changed, 333 insertions(+), 100 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index b875b1fce4288..444132024596e 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -36,6 +36,7 @@ | [isSavedObjectEmbeddableInput(input)](./kibana-plugin-plugins-embeddable-public.issavedobjectembeddableinput.md) | | | [openAddPanelFlyout(options)](./kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-embeddable-public.plugin.md) | | +| [useEmbeddableFactory({ input, factory, onInputUpdated, })](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) | | ## Interfaces diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md new file mode 100644 index 0000000000000..9af20cacc2cee --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [useEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) + +## useEmbeddableFactory() function + +Signature: + +```typescript +export declare function useEmbeddableFactory({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory): readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { input, factory, onInputUpdated, } | EmbeddableRendererWithFactory<I> | | + +Returns: + +`readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]` + diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 25d080dbfd546..80171e1ad2fab 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -69,6 +69,7 @@ export { EmbeddablePackageState, EmbeddableRenderer, EmbeddableRendererProps, + useEmbeddableFactory, } from './lib'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx index 457852c48ed77..b919672ad01e3 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx @@ -9,14 +9,39 @@ import React from 'react'; import { waitFor } from '@testing-library/dom'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import { HelloWorldEmbeddable, HelloWorldEmbeddableFactoryDefinition, HELLO_WORLD_EMBEDDABLE, } from '../../tests/fixtures'; -import { EmbeddableRenderer } from './embeddable_renderer'; +import { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer'; import { embeddablePluginMock } from '../../mocks'; +describe('useEmbeddableFactory', () => { + it('should update upstream value changes', async () => { + const { setup, doStart } = embeddablePluginMock.createInstance(); + const getFactory = setup.registerEmbeddableFactory( + HELLO_WORLD_EMBEDDABLE, + new HelloWorldEmbeddableFactoryDefinition() + ); + doStart(); + + const { result, waitForNextUpdate } = renderHook(() => + useEmbeddableFactory({ factory: getFactory(), input: { id: 'hello' } }) + ); + + const [, loading] = result.current; + + expect(loading).toBe(true); + + await waitForNextUpdate(); + + const [embeddable] = result.current; + expect(embeddable).toBeDefined(); + }); +}); + describe('', () => { test('Render embeddable', () => { const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx index 153564187d4b5..433b21e92cce5 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx @@ -28,12 +28,6 @@ interface EmbeddableRendererPropsWithEmbeddable { embeddable: IEmbeddable; } -function isWithEmbeddable( - props: EmbeddableRendererProps -): props is EmbeddableRendererPropsWithEmbeddable { - return 'embeddable' in props; -} - interface EmbeddableRendererWithFactory { input: I; onInputUpdated?: (newInput: I) => void; @@ -46,6 +40,72 @@ function isWithFactory( return 'factory' in props; } +export function useEmbeddableFactory({ + input, + factory, + onInputUpdated, +}: EmbeddableRendererWithFactory) { + const [embeddable, setEmbeddable] = useState | ErrorEmbeddable | undefined>( + undefined + ); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const latestInput = React.useRef(input); + useEffect(() => { + latestInput.current = input; + }, [input]); + + useEffect(() => { + let canceled = false; + + // keeping track of embeddables created by this component to be able to destroy them + let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined; + setEmbeddable(undefined); + setLoading(true); + factory + .create(latestInput.current!) + .then((createdEmbeddable) => { + if (canceled) { + if (createdEmbeddable) { + createdEmbeddable.destroy(); + } + } else { + createdEmbeddableRef = createdEmbeddable; + setEmbeddable(createdEmbeddable); + } + }) + .catch((err) => { + if (canceled) return; + setError(err?.message); + }) + .finally(() => { + if (canceled) return; + setLoading(false); + }); + + return () => { + canceled = true; + if (createdEmbeddableRef) { + createdEmbeddableRef.destroy(); + } + }; + }, [factory]); + + useEffect(() => { + if (!embeddable) return; + if (isErrorEmbeddable(embeddable)) return; + if (!onInputUpdated) return; + const sub = embeddable.getInput$().subscribe((newInput) => { + onInputUpdated(newInput); + }); + return () => { + sub.unsubscribe(); + }; + }, [embeddable, onInputUpdated]); + + return [embeddable, loading, error] as const; +} + /** * Helper react component to render an embeddable * Can be used if you have an embeddable object or an embeddable factory @@ -82,72 +142,22 @@ function isWithFactory( export const EmbeddableRenderer = ( props: EmbeddableRendererProps ) => { - const { input, onInputUpdated } = props; - const [embeddable, setEmbeddable] = useState | ErrorEmbeddable | undefined>( - isWithEmbeddable(props) ? props.embeddable : undefined - ); - const [loading, setLoading] = useState(!isWithEmbeddable(props)); - const [error, setError] = useState(); - const latestInput = React.useRef(props.input); - useEffect(() => { - latestInput.current = input; - }, [input]); - - const factoryFromProps = isWithFactory(props) ? props.factory : undefined; - const embeddableFromProps = isWithEmbeddable(props) ? props.embeddable : undefined; - useEffect(() => { - let canceled = false; - if (embeddableFromProps) { - setEmbeddable(embeddableFromProps); - return; - } - - // keeping track of embeddables created by this component to be able to destroy them - let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined; - if (factoryFromProps) { - setEmbeddable(undefined); - setLoading(true); - factoryFromProps - .create(latestInput.current!) - .then((createdEmbeddable) => { - if (canceled) { - if (createdEmbeddable) { - createdEmbeddable.destroy(); - } - } else { - createdEmbeddableRef = createdEmbeddable; - setEmbeddable(createdEmbeddable); - } - }) - .catch((err) => { - if (canceled) return; - setError(err?.message); - }) - .finally(() => { - if (canceled) return; - setLoading(false); - }); - } - - return () => { - canceled = true; - if (createdEmbeddableRef) { - createdEmbeddableRef.destroy(); - } - }; - }, [factoryFromProps, embeddableFromProps]); - - useEffect(() => { - if (!embeddable) return; - if (isErrorEmbeddable(embeddable)) return; - if (!onInputUpdated) return; - const sub = embeddable.getInput$().subscribe((newInput) => { - onInputUpdated(newInput); - }); - return () => { - sub.unsubscribe(); - }; - }, [embeddable, onInputUpdated]); + if (isWithFactory(props)) { + return ; + } + return ; +}; +// +const EmbeddableByFactory = ({ + factory, + input, + onInputUpdated, +}: EmbeddableRendererWithFactory) => { + const [embeddable, loading, error] = useEmbeddableFactory({ + factory, + input, + onInputUpdated, + }); return ; }; diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index 71dfd73e534e7..eede745f31794 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -16,4 +16,8 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; export { EmbeddableRoot } from './embeddable_root'; export * from '../../../common/lib/saved_object_embeddable'; -export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer'; +export { + EmbeddableRenderer, + EmbeddableRendererProps, + useEmbeddableFactory, +} from './embeddable_renderer'; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 22001608f63ab..1b3e0388e9bb0 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -542,3 +542,40 @@ test('Check when hide header option is true', async () => { const title = findTestSubject(component, `embeddablePanelHeading-HelloAryaStark`); expect(title.length).toBe(0); }); + +test('Should work in minimal way rendering only the inspector action', async () => { + const inspector = inspectorPluginMock.createStartContract(); + inspector.isAvailable = jest.fn(() => true); + + const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, { + getEmbeddableFactory, + } as any); + + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Arya', + lastName: 'Stark', + }); + + const component = mount( + + Promise.resolve([])} + inspector={inspector} + hideHeader={false} + /> + + ); + + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + expect(findTestSubject(component, `embeddablePanelContextMenuOpen`).length).toBe(1); + await nextTick(); + component.update(); + expect(findTestSubject(component, `embeddablePanelAction-openInspector`).length).toBe(1); + const action = findTestSubject(component, `embeddablePanelAction-ACTION_CUSTOMIZE_PANEL`); + expect(action.length).toBe(0); +}); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 8cf2de8c80743..b66950c170d69 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -54,16 +54,20 @@ const removeById = (disabledActions: string[]) => ({ id }: { id: string }) => interface Props { embeddable: IEmbeddable; getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - application: CoreStart['application']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory']; + getAllEmbeddableFactories?: EmbeddableStart['getEmbeddableFactories']; + overlays?: CoreStart['overlays']; + notifications?: CoreStart['notifications']; + application?: CoreStart['application']; + inspector?: InspectorStartContract; + SavedObjectFinder?: React.ComponentType; stateTransfer?: EmbeddableStateTransfer; hideHeader?: boolean; + actionPredicate?: (actionId: string) => boolean; reportUiCounter?: UsageCollectionStart['reportUiCounter']; + showShadow?: boolean; + showBadges?: boolean; + showNotifications?: boolean; } interface State { @@ -80,7 +84,11 @@ interface State { errorEmbeddable?: ErrorEmbeddable; } -interface PanelUniversalActions { +interface InspectorPanelAction { + inspectPanel: InspectPanelAction; +} + +interface BasePanelActions { customizePanelTitle: CustomizePanelTitleAction; addPanel: AddPanelAction; inspectPanel: InspectPanelAction; @@ -88,6 +96,15 @@ interface PanelUniversalActions { editPanel: EditPanelAction; } +const emptyObject = {}; +type EmptyObject = typeof emptyObject; + +type PanelUniversalActions = + | BasePanelActions + | InspectorPanelAction + | (BasePanelActions & InspectorPanelAction) + | EmptyObject; + export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; @@ -117,10 +134,15 @@ export class EmbeddablePanel extends React.Component { } private async refreshBadges() { + if (!this.mounted) { + return; + } + if (this.props.showBadges === false) { + return; + } let badges = await this.props.getActions(PANEL_BADGE_TRIGGER, { embeddable: this.props.embeddable, }); - if (!this.mounted) return; const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { @@ -135,10 +157,15 @@ export class EmbeddablePanel extends React.Component { } private async refreshNotifications() { + if (!this.mounted) { + return; + } + if (this.props.showNotifications === false) { + return; + } let notifications = await this.props.getActions(PANEL_NOTIFICATION_TRIGGER, { embeddable: this.props.embeddable, }); - if (!this.mounted) return; const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { @@ -229,13 +256,18 @@ export class EmbeddablePanel extends React.Component { paddingSize="none" role="figure" aria-labelledby={headerId} + hasShadow={this.props.showShadow} > {!this.props.hideHeader && ( { }; private getUniversalActions = (): PanelUniversalActions => { + let actions = {}; + if (this.props.inspector) { + actions = { + inspectPanel: new InspectPanelAction(this.props.inspector), + }; + } + if ( + !this.props.getEmbeddableFactory || + !this.props.getAllEmbeddableFactories || + !this.props.overlays || + !this.props.notifications || + !this.props.SavedObjectFinder || + !this.props.application + ) { + return actions; + } + const createGetUserData = (overlays: OverlayStart) => async function getUserData(context: { embeddable: IEmbeddable }) { return new Promise<{ title: string | undefined; hideTitle?: boolean }>((resolve) => { @@ -308,6 +357,7 @@ export class EmbeddablePanel extends React.Component { // Universal actions are exposed on the context menu for every embeddable, they bypass the trigger // registry. return { + ...actions, customizePanelTitle: new CustomizePanelTitleAction(createGetUserData(this.props.overlays)), addPanel: new AddPanelAction( this.props.getEmbeddableFactory, @@ -317,7 +367,6 @@ export class EmbeddablePanel extends React.Component { this.props.SavedObjectFinder, this.props.reportUiCounter ), - inspectPanel: new InspectPanelAction(this.props.inspector), removePanel: new RemovePanelAction(), editPanel: new EditPanelAction( this.props.getEmbeddableFactory, @@ -338,9 +387,13 @@ export class EmbeddablePanel extends React.Component { regularActions = regularActions.filter(removeDisabledActions); } - const sortedActions = [...regularActions, ...Object.values(this.state.universalActions)].sort( - sortByOrderField - ); + let sortedActions = regularActions + .concat(Object.values(this.state.universalActions || {}) as Array>) + .sort(sortByOrderField); + + if (this.props.actionPredicate) { + sortedActions = sortedActions.filter(({ id }) => this.props.actionPredicate!(id)); + } return await buildContextMenuForActions({ actions: sortedActions.map((action) => ({ diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 403aa3e3f4c9f..742a2d1909941 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -36,7 +36,7 @@ export interface PanelHeaderProps { embeddable: IEmbeddable; headerId?: string; showPlaceholderTitle?: boolean; - customizeTitle: CustomizePanelTitleAction; + customizeTitle?: CustomizePanelTitleAction; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -177,7 +177,7 @@ export function PanelHeader({ > {title || placeholderTitle} - ) : ( + ) : customizeTitle ? ( {title || placeholderTitle} - ); + ) : null; } return description ? ( ({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory): readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]; + // Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/x-pack/examples/embedded_lens_example/kibana.json b/x-pack/examples/embedded_lens_example/kibana.json index 5e4caead90bc3..be38e5bf3e761 100644 --- a/x-pack/examples/embedded_lens_example/kibana.json +++ b/x-pack/examples/embedded_lens_example/kibana.json @@ -8,6 +8,7 @@ "requiredPlugins": [ "lens", "data", + "embeddable", "developerExamples" ], "optionalPlugins": [], diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index 6a39951ad4958..a13ddbbd79ef0 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/eui'; import { IndexPattern } from 'src/plugins/data/public'; import { CoreStart } from 'kibana/public'; +import { ViewMode } from '../../../../src/plugins/embeddable/public'; import { TypedLensByValueInput, PersistedIndexPatternLayer, @@ -193,6 +194,7 @@ export const App = (props: {
    { // call back event for on table row click event }} + viewMode={ViewMode.VIEW} /> {isSaveModalVisible && ( & { | LensAttributes<'lnsMetric', MetricState>; }; -export type EmbeddableComponentProps = TypedLensByValueInput | LensByReferenceInput; +export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & { + withActions?: boolean; +}; + +interface PluginsStartDependencies { + uiActions: UiActionsStart; + embeddable: EmbeddableStart; + inspector: InspectorStartContract; +} -export function getEmbeddableComponent(embeddableStart: EmbeddableStart) { +export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDependencies) { return (props: EmbeddableComponentProps) => { + const { embeddable: embeddableStart, uiActions, inspector } = plugins; const factory = embeddableStart.getEmbeddableFactory('lens')!; - return ; + const input = { ...props }; + const [embeddable, loading, error] = useEmbeddableFactory({ factory, input }); + const hasActions = props.withActions === true; + + if (embeddable && hasActions) { + return ( + } + uiActions={uiActions} + inspector={inspector} + actionPredicate={() => hasActions} + input={input} + /> + ); + } + + return ; }; } + +interface EmbeddablePanelWrapperProps { + embeddable: IEmbeddable; + uiActions: PluginsStartDependencies['uiActions']; + inspector: PluginsStartDependencies['inspector']; + actionPredicate: (id: string) => boolean; + input: EmbeddableComponentProps; +} + +const EmbeddablePanelWrapper: FC = ({ + embeddable, + uiActions, + actionPredicate, + inspector, + input, +}) => { + useEffect(() => { + embeddable.updateInput(input); + }, [embeddable, input]); + + return ( + } + getActions={uiActions.getTriggerCompatibleActions} + inspector={inspector} + actionPredicate={actionPredicate} + showShadow={false} + showBadges={false} + showNotifications={false} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 98e0198b9d0fa..a82bc0b9c0d3d 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -7,7 +7,7 @@ import { LensPlugin } from './plugin'; -export { +export type { EmbeddableComponentProps, TypedLensByValueInput, } from './editor_frame_service/embeddable/embeddable_component'; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 328bea5def557..ad81413e21345 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -6,6 +6,7 @@ */ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; +import type { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; @@ -81,6 +82,7 @@ export interface LensPluginStartDependencies { savedObjectsTagging?: SavedObjectTaggingPluginStart; presentationUtil: PresentationUtilPluginStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; + inspector: InspectorStartContract; usageCollection?: UsageCollectionStart; } @@ -256,7 +258,7 @@ export class LensPlugin { ); return { - EmbeddableComponent: getEmbeddableComponent(startDependencies.embeddable), + EmbeddableComponent: getEmbeddableComponent(core, startDependencies), SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!), navigateToPrefilledEditor: (input: LensEmbeddableInput, openInNewTab?: boolean) => { // for openInNewTab, we set the time range in url via getEditPath below diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx index 6cdb01a992034..5b1ba3bcc206b 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx @@ -78,7 +78,9 @@ export class CustomTimeRangeAction implements Action { const isMarkdown = isVisualizeEmbeddable(embeddable) && (embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'markdown'; - return Boolean(embeddable && hasTimeRange(embeddable) && !isInputControl && !isMarkdown); + return Boolean( + embeddable && embeddable.parent && hasTimeRange(embeddable) && !isInputControl && !isMarkdown + ); } public async execute({ embeddable }: TimeRangeActionContext) { From d16a464a4cf5e53d1a83440669f143c4fcf56753 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Fri, 25 Jun 2021 13:13:57 -0400 Subject: [PATCH 26/40] [Lens] Document common formulas in product and add formula tutorial (#103154) * [Lens] Document common formulas in product and add formula tutorial * Make common formulas appear in sidebar and format consistently Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/user/dashboard/lens-advanced.asciidoc | 43 +++++--- .../formula/editor/formula_help.tsx | 98 +++++++++++++++++-- 2 files changed, 121 insertions(+), 20 deletions(-) diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index ec8d90aa4920e..33e0e362058f4 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -104,7 +104,7 @@ To quickly create many copies of a percentile metric that shows distribution of . From the *Chart Type* dropdown, select *Line*. + [role="screenshot"] -image::images/lens_advanced_2_1.png[Chart type menu with Line selected] +image::images/lens_advanced_2_1.png[Chart type menu with Line selected, width=50%] . From the *Available fields* list, drag and drop *products.price* to the visualization builder. @@ -239,12 +239,11 @@ For each category type that you want to break down, create a filter. Change the legend position to the top of the chart. . From the *Legend* dropdown, select the top position. - + [role="screenshot"] image::images/lens_advanced_4_1.png[Prices share by category] - Click *Save and return*. +. Click *Save and return*. [discrete] [[view-the-cumulative-number-of-products-sold-on-weekends]] @@ -299,7 +298,8 @@ image::images/lens_advanced_5_2.png[Line chart with cumulative sum of orders mad [[compare-time-ranges]] === Compare time ranges -*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option. +*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option. To calculate the percent +change, use *Formula*. Time shifts can be used on any metric. The special shift *previous* will show the time window preceding the currently selected one, spanning the same duration. For example, if *Last 7 days* is selected in the time filter, *previous* will show data from 14 days ago to 7 days ago. @@ -326,9 +326,32 @@ To compare current sales numbers with sales from a week ago, follow these steps: .. Click *Time shift* .. Click the *1 week* option. You can also define custom shifts by typing amount followed by time unit (like *1w* for a one week shift), then hit enter. - ++ [role="screenshot"] -image::images/lens_time_shift.png[Line chart with week-over-week sales comparison] +image::images/lens_time_shift.png[Line chart with week-over-week sales comparison, width=50%] + +. Click *Save and return*. + +[float] +[[compare-time-as-percent]] +==== Compare time ranges as a percent change + +To view the percent change in sales between the current time and the previous week, use a *Formula*: + +. Open *Lens*. + +. From the *Available fields* list, drag and drop *Records* to the visualization builder. + +. Click *Count of Records*, then click *Formula*. + +. Type `count() / count(shift='1w') - 1`. To learn more about the formula +syntax, click *Help*. + +. Click *Value format* and select *Percent* with 0 decimals. + +. In the *Display name* field, enter `Percent change`, then click *Close*. + +. Click *Save and return*. [discrete] [[view-customers-over-time-by-continents]] @@ -366,18 +389,14 @@ To split the customers count by continent: . From the *Available fields* list, drag and drop *geoip.continent_name* to the *Columns* field of the editor. + [role="screenshot"] -image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration] +image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration, width=50%] . Click *Save and return*. + [discrete] === Save the dashboard -By default the dashboard attempts to match the palette across panels, but in this case there's no need for that, so it can be disabled. - -[role="screenshot"] -image::images/lens_advanced_7_1.png[Disable palette sync in dashboard] - Now that you have a complete overview of your ecommerce sales data, save the dashboard. . In the toolbar, click *Save*. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx index aa08dfbe7ea33..dbdbcf226d9bb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_help.tsx @@ -64,6 +64,88 @@ function FormulaHelp({ items: [], }); + helpGroups.push({ + label: i18n.translate('xpack.lens.formulaFrequentlyUsedHeading', { + defaultMessage: 'Common formulas', + }), + description: i18n.translate('xpack.lens.formulaCommonFormulaDocumentation', { + defaultMessage: `The most common formulas are dividing two values to produce a percent. To display accurately, set "value format" to "percent".`, + }), + + items: [ + { + label: i18n.translate('xpack.lens.formulaDocumentation.filterRatio', { + defaultMessage: 'Filter ratio', + }), + description: ( + + ), + }, + { + label: i18n.translate('xpack.lens.formulaDocumentation.weekOverWeek', { + defaultMessage: 'Week over week', + }), + description: ( + + ), + }, + { + label: i18n.translate('xpack.lens.formulaDocumentation.percentOfTotal', { + defaultMessage: 'Percent of total', + }), + description: ( + + ), + }, + ], + }); + helpGroups.push({ label: i18n.translate('xpack.lens.formulaDocumentation.elasticsearchSection', { defaultMessage: 'Elasticsearch', @@ -78,7 +160,7 @@ function FormulaHelp({ const availableFunctions = getPossibleFunctions(indexPattern); // Es aggs - helpGroups[1].items.push( + helpGroups[2].items.push( ...availableFunctions .filter( (key) => @@ -104,20 +186,20 @@ function FormulaHelp({ helpGroups.push({ label: i18n.translate('xpack.lens.formulaDocumentation.columnCalculationSection', { - defaultMessage: 'Column-wise calculation', + defaultMessage: 'Column calculations', }), description: i18n.translate( 'xpack.lens.formulaDocumentation.columnCalculationSectionDescription', { defaultMessage: - 'These functions will be executed for reach row of the resulting table, using data from cells from other rows as well as the current value.', + 'These functions are executed for each row, but are provided with the whole column as context. This is also known as a window function.', } ), items: [], }); // Calculations aggs - helpGroups[2].items.push( + helpGroups[3].items.push( ...availableFunctions .filter( (key) => @@ -170,7 +252,7 @@ function FormulaHelp({ }); }, [indexPattern]); - helpGroups[3].items.push( + helpGroups[4].items.push( ...tinymathFns.map(({ label, description, examples }) => { return { label, @@ -312,9 +394,9 @@ round(100 * moving_average( \`\`\` Elasticsearch functions take a field name, which can be in quotes. \`sum(bytes)\` is the same -as \`sum("bytes")\`. +as \`sum('bytes')\`. -Some functions take named arguments, like moving_average(count(), window=5) +Some functions take named arguments, like \`moving_average(count(), window=5)\`. Elasticsearch metrics can be filtered using KQL or Lucene syntax. To add a filter, use the named parameter \`kql='field: value'\` or \`lucene=''\`. Always use single quotes when writing KQL or Lucene @@ -325,7 +407,7 @@ Math functions can take positional arguments, like pow(count(), 3) is the same a Use the symbols +, -, /, and * to perform basic math. `, description: - 'Text is in markdown. Do not translate function names or field names like sum(bytes)', + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', })} /> From 672653776d0361ddf2fe6c501520bdf42949be00 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Fri, 25 Jun 2021 20:41:37 +0200 Subject: [PATCH 27/40] Allow additive csp configuration (#102059) * add additive csp configuration * add unit tests for new class * fix types * adapt test utils * fix tests * more unit tests on config * generated doc * review comments * update ascii doc * update ascii doc links * automatically add single quotes for keywords * add missing csp directives * add more tests * add additional settings to asciidoc * add null-check * revert test config props * fix usage collection usage * some review comments * last review comments * add kibana-docker variables * try to fix doc reference * try to fix doc reference again * fix tests --- ...plugin-core-server.cspconfig.__private_.md | 11 + .../kibana-plugin-core-server.cspconfig.md | 1 + docs/setup/settings.asciidoc | 51 +- src/core/server/csp/config.test.ts | 460 +++++++++++++++++- src/core/server/csp/config.ts | 154 +++++- src/core/server/csp/csp_config.test.ts | 109 ++++- src/core/server/csp/csp_config.ts | 28 +- src/core/server/csp/csp_directives.test.ts | 266 ++++++++++ src/core/server/csp/csp_directives.ts | 159 ++++++ .../http/cookie_session_storage.test.ts | 6 +- src/core/server/http/http_config.test.ts | 5 +- .../lifecycle_handlers.test.ts | 6 +- src/core/server/http/test_utils.ts | 6 +- src/core/server/server.api.md | 6 +- .../resources/base/bin/kibana-docker | 11 + .../collectors/csp/csp_collector.test.ts | 16 +- 16 files changed, 1244 insertions(+), 51 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md create mode 100644 src/core/server/csp/csp_directives.test.ts create mode 100644 src/core/server/csp/csp_directives.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md new file mode 100644 index 0000000000000..217066481d33c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CspConfig](./kibana-plugin-core-server.cspconfig.md) > ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) + +## CspConfig."\#private" property + +Signature: + +```typescript +#private; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md index 9f4f3211ea2b1..0337a1f4d3301 100644 --- a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md @@ -20,6 +20,7 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) | | | | | [DEFAULT](./kibana-plugin-core-server.cspconfig.default.md) | static | CspConfig | | | [disableEmbedding](./kibana-plugin-core-server.cspconfig.disableembedding.md) | | boolean | | | [header](./kibana-plugin-core-server.cspconfig.header.md) | | string | | diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index c3c29adcea18f..bcaa86d73adc4 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -36,11 +36,57 @@ Set to `false` to disable Console. *Default: `true`* <>. | `csp.rules:` - | A https://w3c.github.io/webappsec-csp/[content-security-policy] template + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] +A https://w3c.github.io/webappsec-csp/[Content Security Policy] template that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules that ship with {kib}. +| `csp.script_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src[Content Security Policy `script-src` directive]. + +| `csp.worker_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src[Content Security Policy `worker-src` directive]. + +| `csp.style_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src[Content Security Policy `style-src` directive]. + +| `csp.connect_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src[Content Security Policy `connect-src` directive]. + +| `csp.default_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src[Content Security Policy `default-src` directive]. + +| `csp.font_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src[Content Security Policy `font-src` directive]. + +| `csp.frame_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src[Content Security Policy `frame-src` directive]. + +| `csp.img_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src[Content Security Policy `img-src` directive]. + +| `csp.frame_ancestors:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors[Content Security Policy `frame-ancestors` directive]. + +|=== + +[NOTE] +============ +The `frame-ancestors` directive can also be configured by using +<>. In that case, that takes precedence and any values in `csp.frame_ancestors` +are ignored. +============ + +[cols="2*<"] +|=== + +| `csp.report_uri:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri[Content Security Policy `report-uri` directive]. + +| `csp.report_to:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to[Content Security Policy `report-to` directive]. + |[[csp-strict]] `csp.strict:` | Blocks {kib} access to any browser that does not enforce even rudimentary CSP rules. In practice, this disables @@ -538,8 +584,7 @@ a|`server.securityResponseHeaders:` is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or `null`. To disable, set to `null`. *Default:* `null` -[[server-securityResponseHeaders-disableEmbedding]] -a|`server.securityResponseHeaders:` +|[[server-securityResponseHeaders-disableEmbedding]]`server.securityResponseHeaders:` `disableEmbedding:` | Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy[`Content-Security-Policy`] and https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options[`X-Frame-Options`] headers are configured to disable embedding diff --git a/src/core/server/csp/config.test.ts b/src/core/server/csp/config.test.ts index 8036ebeeaad31..6db93addb7da8 100644 --- a/src/core/server/csp/config.test.ts +++ b/src/core/server/csp/config.test.ts @@ -9,11 +9,469 @@ import { config } from './config'; describe('config.validate()', () => { - test(`does not allow "disableEmbedding" to be set to true`, () => { + it(`does not allow "disableEmbedding" to be set to true`, () => { // This is intentionally not editable in the raw CSP config. // Users should set `server.securityResponseHeaders.disableEmbedding` to control this config property. expect(() => config.schema.validate({ disableEmbedding: true })).toThrowError( '[disableEmbedding]: expected value to equal [false]' ); }); + + describe(`"script_src"`, () => { + it(`throws if containing 'unsafe-inline' when 'strict' is true`, () => { + expect(() => + config.schema.validate({ + strict: true, + warnLegacyBrowsers: false, + script_src: [`'self'`, `unsafe-inline`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"` + ); + + expect(() => + config.schema.validate({ + strict: true, + warnLegacyBrowsers: false, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"` + ); + }); + + it(`throws if containing 'unsafe-inline' when 'warnLegacyBrowsers' is true`, () => { + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: true, + script_src: [`'self'`, `unsafe-inline`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"` + ); + + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: true, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"` + ); + }); + + it(`does not throw if containing 'unsafe-inline' when 'strict' and 'warnLegacyBrowsers' are false`, () => { + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: false, + script_src: [`'self'`, `unsafe-inline`], + }) + ).not.toThrow(); + + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: false, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).not.toThrow(); + }); + + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + script_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + script_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + script_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + script_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"worker_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + worker_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + worker_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + worker_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + worker_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"style_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + style_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + style_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + style_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + style_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"connect_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + connect_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + connect_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + connect_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + connect_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"default_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + default_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + default_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + default_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + default_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"font_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + font_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + font_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + font_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + font_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"frame_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + frame_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + frame_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + frame_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + frame_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"img_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + img_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + img_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + img_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + img_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"frame_ancestors"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + frame_ancestors: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); }); diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts index a61fa1b03a45c..3a7cb20985cea 100644 --- a/src/core/server/csp/config.ts +++ b/src/core/server/csp/config.ts @@ -7,28 +7,150 @@ */ import { TypeOf, schema } from '@kbn/config-schema'; +import { ServiceConfigDescriptor } from '../internal_types'; + +interface DirectiveValidationOptions { + allowNone: boolean; + allowNonce: boolean; +} + +const getDirectiveValidator = (options: DirectiveValidationOptions) => { + const validateValue = getDirectiveValueValidator(options); + return (values: string[]) => { + for (const value of values) { + const error = validateValue(value); + if (error) { + return error; + } + } + }; +}; + +const getDirectiveValueValidator = ({ allowNone, allowNonce }: DirectiveValidationOptions) => { + return (value: string) => { + if (!allowNonce && value.startsWith('nonce-')) { + return `using "nonce-*" is considered insecure and is not allowed`; + } + if (!allowNone && (value === `none` || value === `'none'`)) { + return `using "none" would conflict with Kibana's default csp configuration and is not allowed`; + } + }; +}; + +const configSchema = schema.object( + { + rules: schema.maybe(schema.arrayOf(schema.string())), + script_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + worker_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + style_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + connect_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + default_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + font_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + frame_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + img_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + frame_ancestors: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + report_uri: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: true, allowNonce: false }), + }), + report_to: schema.arrayOf(schema.string(), { + defaultValue: [], + }), + strict: schema.boolean({ defaultValue: true }), + warnLegacyBrowsers: schema.boolean({ defaultValue: true }), + disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }), + }, + { + validate: (cspConfig) => { + if (cspConfig.rules && hasDirectiveSpecified(cspConfig)) { + return `"csp.rules" cannot be used when specifying per-directive additions such as "script_src", "worker_src" or "style_src"`; + } + const hasUnsafeInlineScriptSrc = + cspConfig.script_src.includes(`unsafe-inline`) || + cspConfig.script_src.includes(`'unsafe-inline'`); + + if (cspConfig.strict && hasUnsafeInlineScriptSrc) { + return 'cannot use `unsafe-inline` for `script_src` when `csp.strict` is true'; + } + if (cspConfig.warnLegacyBrowsers && hasUnsafeInlineScriptSrc) { + return 'cannot use `unsafe-inline` for `script_src` when `csp.warnLegacyBrowsers` is true'; + } + }, + } +); + +const hasDirectiveSpecified = (rawConfig: CspConfigType): boolean => { + return Boolean( + rawConfig.script_src.length || + rawConfig.worker_src.length || + rawConfig.style_src.length || + rawConfig.connect_src.length || + rawConfig.default_src.length || + rawConfig.font_src.length || + rawConfig.frame_src.length || + rawConfig.img_src.length || + rawConfig.frame_ancestors.length || + rawConfig.report_uri.length || + rawConfig.report_to.length + ); +}; /** * @internal */ -export type CspConfigType = TypeOf; +export type CspConfigType = TypeOf; -export const config = { +export const config: ServiceConfigDescriptor = { // TODO: Move this to server.csp using config deprecations // ? https://github.com/elastic/kibana/pull/52251 path: 'csp', - schema: schema.object({ - rules: schema.arrayOf(schema.string(), { - defaultValue: [ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, - ], - }), - strict: schema.boolean({ defaultValue: true }), - warnLegacyBrowsers: schema.boolean({ defaultValue: true }), - disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }), - }), + schema: configSchema, + deprecations: () => [ + (rawConfig, fromPath, addDeprecation) => { + const cspConfig = rawConfig[fromPath]; + if (cspConfig?.rules) { + addDeprecation({ + message: + '`csp.rules` is deprecated in favor of directive specific configuration. Please use `csp.connect_src`, ' + + '`csp.default_src`, `csp.font_src`, `csp.frame_ancestors`, `csp.frame_src`, `csp.img_src`, ' + + '`csp.report_uri`, `csp.report_to`, `csp.script_src`, `csp.style_src`, and `csp.worker_src` instead.', + correctiveActions: { + manualSteps: [ + `Remove "csp.rules" from the Kibana config file."`, + `Add directive specific configurations to the config file using "csp.connect_src", "csp.default_src", "csp.font_src", ` + + `"csp.frame_ancestors", "csp.frame_src", "csp.img_src", "csp.report_uri", "csp.report_to", "csp.script_src", ` + + `"csp.style_src", and "csp.worker_src".`, + ], + }, + }); + } + }, + ], }; - -export const FRAME_ANCESTORS_RULE = `frame-ancestors 'self'`; // only used by CspConfig when embedding is disabled diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 1e023c6f08ea8..a1bac7d4ae73e 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -7,7 +7,7 @@ */ import { CspConfig } from './csp_config'; -import { FRAME_ANCESTORS_RULE } from './config'; +import { config as cspConfig, CspConfigType } from './config'; // CSP rules aren't strictly additive, so any change can potentially expand or // restrict the policy in a way we consider a breaking change. For that reason, @@ -23,6 +23,12 @@ import { FRAME_ANCESTORS_RULE } from './config'; // the nature of a change in defaults during a PR review. describe('CspConfig', () => { + let defaultConfig: CspConfigType; + + beforeEach(() => { + defaultConfig = cspConfig.schema.validate({}); + }); + test('DEFAULT', () => { expect(CspConfig.DEFAULT).toMatchInlineSnapshot(` CspConfig { @@ -40,50 +46,129 @@ describe('CspConfig', () => { }); test('defaults from config', () => { - expect(new CspConfig()).toEqual(CspConfig.DEFAULT); + expect(new CspConfig(defaultConfig)).toEqual(CspConfig.DEFAULT); }); describe('partial config', () => { test('allows "rules" to be set and changes header', () => { - const rules = ['foo', 'bar']; - const config = new CspConfig({ rules }); + const rules = [`foo 'self'`, `bar 'self'`]; + const config = new CspConfig({ ...defaultConfig, rules }); expect(config.rules).toEqual(rules); - expect(config.header).toMatchInlineSnapshot(`"foo; bar"`); + expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`); }); test('allows "strict" to be set', () => { - const config = new CspConfig({ strict: false }); + const config = new CspConfig({ ...defaultConfig, strict: false }); expect(config.strict).toEqual(false); expect(config.strict).not.toEqual(CspConfig.DEFAULT.strict); }); test('allows "warnLegacyBrowsers" to be set', () => { const warnLegacyBrowsers = false; - const config = new CspConfig({ warnLegacyBrowsers }); + const config = new CspConfig({ ...defaultConfig, warnLegacyBrowsers }); expect(config.warnLegacyBrowsers).toEqual(warnLegacyBrowsers); expect(config.warnLegacyBrowsers).not.toEqual(CspConfig.DEFAULT.warnLegacyBrowsers); }); + test('allows "worker_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + worker_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`worker-src foo bar`]); + expect(config.header).toEqual(`worker-src foo bar`); + }); + + test('allows "style_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + style_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`style-src foo bar`]); + expect(config.header).toEqual(`style-src foo bar`); + }); + + test('allows "script_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + script_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`script-src foo bar`]); + expect(config.header).toEqual(`script-src foo bar`); + }); + + test('allows all directives to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + script_src: ['script', 'foo'], + worker_src: ['worker', 'bar'], + style_src: ['style', 'dolly'], + }); + expect(config.rules).toEqual([ + `script-src script foo`, + `worker-src worker bar`, + `style-src style dolly`, + ]); + expect(config.header).toEqual( + `script-src script foo; worker-src worker bar; style-src style dolly` + ); + }); + + test('applies defaults when `rules` is undefined', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: undefined, + script_src: ['script'], + worker_src: ['worker'], + style_src: ['style'], + }); + expect(config.rules).toEqual([ + `script-src 'unsafe-eval' 'self' script`, + `worker-src blob: 'self' worker`, + `style-src 'unsafe-inline' 'self' style`, + ]); + expect(config.header).toEqual( + `script-src 'unsafe-eval' 'self' script; worker-src blob: 'self' worker; style-src 'unsafe-inline' 'self' style` + ); + }); + describe('allows "disableEmbedding" to be set', () => { const disableEmbedding = true; test('and changes rules/header if custom rules are not defined', () => { - const config = new CspConfig({ disableEmbedding }); + const config = new CspConfig({ ...defaultConfig, disableEmbedding }); expect(config.disableEmbedding).toEqual(disableEmbedding); expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); - expect(config.rules).toEqual(expect.arrayContaining([FRAME_ANCESTORS_RULE])); + expect(config.rules).toEqual(expect.arrayContaining([`frame-ancestors 'self'`])); expect(config.header).toMatchInlineSnapshot( `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"` ); }); test('and does not change rules/header if custom rules are defined', () => { - const rules = ['foo', 'bar']; - const config = new CspConfig({ disableEmbedding, rules }); + const rules = [`foo 'self'`, `bar 'self'`]; + const config = new CspConfig({ ...defaultConfig, disableEmbedding, rules }); expect(config.disableEmbedding).toEqual(disableEmbedding); expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); expect(config.rules).toEqual(rules); - expect(config.header).toMatchInlineSnapshot(`"foo; bar"`); + expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`); + }); + + test('and overrides `frame-ancestors` if set', () => { + const config = new CspConfig({ + ...defaultConfig, + disableEmbedding: true, + frame_ancestors: ['foo.com'], + }); + expect(config.disableEmbedding).toEqual(disableEmbedding); + expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); + expect(config.header).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"` + ); }); }); }); diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index 649c81576ef52..13778088d9df2 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { config, FRAME_ANCESTORS_RULE } from './config'; +import { config, CspConfigType } from './config'; +import { CspDirectives } from './csp_directives'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); @@ -50,8 +51,9 @@ export interface ICspConfig { * @public */ export class CspConfig implements ICspConfig { - static readonly DEFAULT = new CspConfig(); + static readonly DEFAULT = new CspConfig(DEFAULT_CONFIG); + readonly #directives: CspDirectives; public readonly rules: string[]; public readonly strict: boolean; public readonly warnLegacyBrowsers: boolean; @@ -62,16 +64,18 @@ export class CspConfig implements ICspConfig { * Returns the default CSP configuration when passed with no config * @internal */ - constructor(rawCspConfig: Partial> = {}) { - const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; - - this.rules = [...source.rules]; - this.strict = source.strict; - this.warnLegacyBrowsers = source.warnLegacyBrowsers; - this.disableEmbedding = source.disableEmbedding; - if (!rawCspConfig.rules?.length && source.disableEmbedding) { - this.rules.push(FRAME_ANCESTORS_RULE); + constructor(rawCspConfig: CspConfigType) { + this.#directives = CspDirectives.fromConfig(rawCspConfig); + if (!rawCspConfig.rules?.length && rawCspConfig.disableEmbedding) { + this.#directives.clearDirectiveValues('frame-ancestors'); + this.#directives.addDirectiveValue('frame-ancestors', `'self'`); } - this.header = this.rules.join('; '); + + this.rules = this.#directives.getRules(); + this.header = this.#directives.getCspHeader(); + + this.strict = rawCspConfig.strict; + this.warnLegacyBrowsers = rawCspConfig.warnLegacyBrowsers; + this.disableEmbedding = rawCspConfig.disableEmbedding; } } diff --git a/src/core/server/csp/csp_directives.test.ts b/src/core/server/csp/csp_directives.test.ts new file mode 100644 index 0000000000000..1077b6ea9f3cd --- /dev/null +++ b/src/core/server/csp/csp_directives.test.ts @@ -0,0 +1,266 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CspDirectives } from './csp_directives'; +import { config as cspConfig } from './config'; + +describe('CspDirectives', () => { + describe('#addDirectiveValue', () => { + it('properly updates the rules', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo", + ] + `); + + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + ] + `); + }); + + it('properly updates the header', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo"`); + + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`); + }); + + it('handles distinct directives', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'bar'); + directives.addDirectiveValue('worker-src', 'dolly'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src foo bar; worker-src dolly"` + ); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + "worker-src dolly", + ] + `); + }); + + it('removes duplicates', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + ] + `); + }); + + it('automatically adds single quotes for keywords', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'none'); + directives.addDirectiveValue('style-src', 'self'); + directives.addDirectiveValue('style-src', 'strict-dynamic'); + directives.addDirectiveValue('style-src', 'report-sample'); + directives.addDirectiveValue('style-src', 'unsafe-inline'); + directives.addDirectiveValue('style-src', 'unsafe-eval'); + directives.addDirectiveValue('style-src', 'unsafe-hashes'); + directives.addDirectiveValue('style-src', 'unsafe-allow-redirects'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"` + ); + }); + + it('does not add single quotes for keywords when already present', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', `'none'`); + directives.addDirectiveValue('style-src', `'self'`); + directives.addDirectiveValue('style-src', `'strict-dynamic'`); + directives.addDirectiveValue('style-src', `'report-sample'`); + directives.addDirectiveValue('style-src', `'unsafe-inline'`); + directives.addDirectiveValue('style-src', `'unsafe-eval'`); + directives.addDirectiveValue('style-src', `'unsafe-hashes'`); + directives.addDirectiveValue('style-src', `'unsafe-allow-redirects'`); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"` + ); + }); + }); + + describe('#fromConfig', () => { + it('returns the correct rules for the default config', () => { + const config = cspConfig.schema.validate({}); + const directives = CspDirectives.fromConfig(config); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ] + `); + }); + + it('returns the correct header for the default config', () => { + const config = cspConfig.schema.validate({}); + const directives = CspDirectives.fromConfig(config); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"` + ); + }); + + it('handles config with rules', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src 'self' http://foo.com`, `worker-src 'self'`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('adds single quotes for keyword for rules', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src self http://foo.com`, `worker-src self`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('handles multiple whitespaces when parsing rules', () => { + const config = cspConfig.schema.validate({ + rules: [` script-src 'self' http://foo.com `, ` worker-src 'self' `], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('supports unregistered directives', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src 'self' http://foo.com`, `img-src 'self'`, 'foo bar'], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "img-src 'self'", + "foo bar", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; img-src 'self'; foo bar"` + ); + }); + + it('adds default value for config with directives', () => { + const config = cspConfig.schema.validate({ + script_src: [`baz`], + worker_src: [`foo`], + style_src: [`bar`, `dolly`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self' baz", + "worker-src blob: 'self' foo", + "style-src 'unsafe-inline' 'self' bar dolly", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self' baz; worker-src blob: 'self' foo; style-src 'unsafe-inline' 'self' bar dolly"` + ); + }); + + it('adds additional values for some directives without defaults', () => { + const config = cspConfig.schema.validate({ + connect_src: [`connect-src`], + default_src: [`default-src`], + font_src: [`font-src`], + frame_src: [`frame-src`], + img_src: [`img-src`], + frame_ancestors: [`frame-ancestors`], + report_uri: [`report-uri`], + report_to: [`report-to`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + "connect-src 'self' connect-src", + "default-src 'self' default-src", + "font-src 'self' font-src", + "frame-src 'self' frame-src", + "img-src 'self' img-src", + "frame-ancestors 'self' frame-ancestors", + "report-uri report-uri", + "report-to report-to", + ] + `); + }); + + it('adds single quotes for keywords in added directives', () => { + const config = cspConfig.schema.validate({ + script_src: [`unsafe-hashes`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self' 'unsafe-hashes'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self' 'unsafe-hashes'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"` + ); + }); + }); +}); diff --git a/src/core/server/csp/csp_directives.ts b/src/core/server/csp/csp_directives.ts new file mode 100644 index 0000000000000..9e3b60f7f1e4f --- /dev/null +++ b/src/core/server/csp/csp_directives.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CspConfigType } from './config'; + +export type CspDirectiveName = + | 'script-src' + | 'worker-src' + | 'style-src' + | 'frame-ancestors' + | 'connect-src' + | 'default-src' + | 'font-src' + | 'frame-src' + | 'img-src' + | 'report-uri' + | 'report-to'; + +/** + * The default rules that are always applied + */ +export const defaultRules: Partial> = { + 'script-src': [`'unsafe-eval'`, `'self'`], + 'worker-src': [`blob:`, `'self'`], + 'style-src': [`'unsafe-inline'`, `'self'`], +}; + +/** + * Per-directive rules that will be added when the configuration contains at least one value + * Main purpose is to add `self` value to some directives when the configuration specifies other values + */ +export const additionalRules: Partial> = { + 'connect-src': [`'self'`], + 'default-src': [`'self'`], + 'font-src': [`'self'`], + 'img-src': [`'self'`], + 'frame-ancestors': [`'self'`], + 'frame-src': [`'self'`], +}; + +export class CspDirectives { + private readonly directives = new Map>(); + + addDirectiveValue(directiveName: CspDirectiveName, directiveValue: string) { + if (!this.directives.has(directiveName)) { + this.directives.set(directiveName, new Set()); + } + this.directives.get(directiveName)!.add(normalizeDirectiveValue(directiveValue)); + } + + clearDirectiveValues(directiveName: CspDirectiveName) { + this.directives.delete(directiveName); + } + + getCspHeader() { + return this.getRules().join('; '); + } + + getRules() { + return [...this.directives.entries()].map(([name, values]) => { + return [name, ...values].join(' '); + }); + } + + static fromConfig(config: CspConfigType): CspDirectives { + const cspDirectives = new CspDirectives(); + + // adding `csp.rules` or `default` rules + const initialRules = config.rules ? parseRules(config.rules) : { ...defaultRules }; + Object.entries(initialRules).forEach(([key, values]) => { + values?.forEach((value) => { + cspDirectives.addDirectiveValue(key as CspDirectiveName, value); + }); + }); + + // adding per-directive configuration + const additiveConfig = parseConfigDirectives(config); + [...additiveConfig.entries()].forEach(([directiveName, directiveValues]) => { + const additionalValues = additionalRules[directiveName] ?? []; + [...additionalValues, ...directiveValues].forEach((value) => { + cspDirectives.addDirectiveValue(directiveName, value); + }); + }); + + return cspDirectives; + } +} + +const parseRules = (rules: string[]): Partial> => { + const directives: Partial> = {}; + rules.forEach((rule) => { + const [name, ...values] = rule.replace(/\s+/g, ' ').trim().split(' '); + directives[name as CspDirectiveName] = values; + }); + return directives; +}; + +const parseConfigDirectives = (cspConfig: CspConfigType): Map => { + const map = new Map(); + + if (cspConfig.script_src?.length) { + map.set('script-src', cspConfig.script_src); + } + if (cspConfig.worker_src?.length) { + map.set('worker-src', cspConfig.worker_src); + } + if (cspConfig.style_src?.length) { + map.set('style-src', cspConfig.style_src); + } + if (cspConfig.connect_src?.length) { + map.set('connect-src', cspConfig.connect_src); + } + if (cspConfig.default_src?.length) { + map.set('default-src', cspConfig.default_src); + } + if (cspConfig.font_src?.length) { + map.set('font-src', cspConfig.font_src); + } + if (cspConfig.frame_src?.length) { + map.set('frame-src', cspConfig.frame_src); + } + if (cspConfig.img_src?.length) { + map.set('img-src', cspConfig.img_src); + } + if (cspConfig.frame_ancestors?.length) { + map.set('frame-ancestors', cspConfig.frame_ancestors); + } + if (cspConfig.report_uri?.length) { + map.set('report-uri', cspConfig.report_uri); + } + if (cspConfig.report_to?.length) { + map.set('report-to', cspConfig.report_to); + } + + return map; +}; + +const keywordTokens = [ + 'none', + 'self', + 'strict-dynamic', + 'report-sample', + 'unsafe-inline', + 'unsafe-eval', + 'unsafe-hashes', + 'unsafe-allow-redirects', +]; + +function normalizeDirectiveValue(value: string) { + if (keywordTokens.includes(value)) { + return `'${value}'`; + } + return value; +} diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index c802163866423..55af02a08561b 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -69,7 +69,11 @@ configService.atPath.mockImplementation((path) => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 56095336d970b..06a4745632233 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { config, HttpConfig } from './http_config'; -import { CspConfig } from '../csp'; +import { config as cspConfig } from '../csp'; import { ExternalUrlConfig } from '../external_url'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost', '0.0.0.0']; @@ -459,7 +459,8 @@ describe('HttpConfig', () => { }, }, }); - const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT, ExternalUrlConfig.DEFAULT); + const rawCspConfig = cspConfig.schema.validate({}); + const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT); expect(httpConfig.customResponseHeaders).toEqual({ string: 'string', diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index cbd300fdc9c09..c2023c5577d61 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -79,7 +79,11 @@ describe('core lifecycle handlers', () => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index b3180b43d0026..4e1a88e967f8f 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -56,7 +56,11 @@ configService.atPath.mockImplementation((path) => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index fcecf39f7e53a..3bc0b54635eb5 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -777,8 +777,12 @@ export interface CountResponse { // @public export class CspConfig implements ICspConfig { + // (undocumented) + #private; + // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts + // // @internal - constructor(rawCspConfig?: Partial>); + constructor(rawCspConfig: CspConfigType); // (undocumented) static readonly DEFAULT: CspConfig; // (undocumented) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index b520ab3070b15..a224793bace3f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -31,6 +31,17 @@ kibana_vars=( csp.rules csp.strict csp.warnLegacyBrowsers + csp.script_src + csp.worker_src + csp.style_src + csp.connect_src + csp.default_src + csp.font_src + csp.frame_src + csp.img_src + csp.frame_ancestors + csp.report_uri + csp.report_to data.autocomplete.valueSuggestions.terminateAfter data.autocomplete.valueSuggestions.timeout elasticsearch.customHeaders diff --git a/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts index fffbe013d98e8..f5a783a6dfb05 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts @@ -22,7 +22,21 @@ describe('csp collector', () => { const mockedFetchContext = createCollectorFetchContextMock(); function updateCsp(config: Partial) { - httpMock.csp = new CspConfig(config); + httpMock.csp = new CspConfig({ + ...CspConfig.DEFAULT, + style_src: [], + worker_src: [], + script_src: [], + connect_src: [], + default_src: [], + font_src: [], + frame_src: [], + img_src: [], + frame_ancestors: [], + report_uri: [], + report_to: [], + ...config, + }); } beforeEach(() => { From c6d04a91952f04b887bd8454a31a96b16ab245b2 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Fri, 25 Jun 2021 21:57:57 +0100 Subject: [PATCH 28/40] [ML] Converts management app jobs list pages to new layout (#103117) * [ML] Converts management app jobs list pages to new layout * i[ML] Fix translations * [ML] Set acccessDenied default state back to false * [ML] Remove headers for error states and text updates following review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/application/access_denied/page.tsx | 63 ++++------- .../components/access_denied_page.tsx | 80 +++++-------- .../components/insufficient_license_page.tsx | 104 +++++++---------- .../jobs_list_page/jobs_list_page.tsx | 106 ++++++++---------- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 6 files changed, 140 insertions(+), 215 deletions(-) diff --git a/x-pack/plugins/ml/public/application/access_denied/page.tsx b/x-pack/plugins/ml/public/application/access_denied/page.tsx index fa535eb1bd53a..bc0d17e96fe71 100644 --- a/x-pack/plugins/ml/public/application/access_denied/page.tsx +++ b/x-pack/plugins/ml/public/application/access_denied/page.tsx @@ -5,21 +5,11 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiCallOut, - EuiPage, - EuiPageBody, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent, EuiSpacer } from '@elastic/eui'; import { NavigationMenu } from '../components/navigation_menu'; import { HelpMenu } from '../components/help_menu'; import { useMlKibana } from '../contexts/kibana'; @@ -30,44 +20,35 @@ export const Page = () => { } = useMlKibana(); const helpLink = docLinks.links.ml.guide; return ( - + <> - - - - - -

    + + + + + -

    -
    -
    -
    - - - - + + } + body={

    -
    -
    -
    -
    -
    + } + /> + +
    + - + ); }; diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx index 0d785f1918b0b..4858db1ee55bc 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx @@ -5,59 +5,39 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { - EuiCallOut, - EuiPage, - EuiPageBody, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import { EuiEmptyPrompt, EuiPageContent } from '@elastic/eui'; export const AccessDeniedPage = () => ( - - - - - - -

    - -

    -
    -
    -
    - - - - -

    - -

    -
    -
    -
    -
    -
    -
    + <> + + + + + } + body={ +

    + +

    + } + /> +
    + ); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx index 22951e950045b..6d0c3639d939c 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx @@ -5,78 +5,54 @@ * 2.0. */ -import React, { FC, Fragment } from 'react'; +import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; -import { - EuiCallOut, - EuiPage, - EuiPageBody, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiSpacer, - EuiText, - EuiTitle, - EuiLink, -} from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLink, EuiPageContent } from '@elastic/eui'; interface Props { basePath: CoreStart['http']['basePath']; } export const InsufficientLicensePage: FC = ({ basePath }) => ( - - - - - - -

    - -

    -
    -
    -
    - - - - -

    - - - - ), - }} - /> -

    -
    -
    -
    -
    -
    -
    + <> + + + + + } + body={ +

    + + + + ), + }} + /> +

    + } + /> +
    + ); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index ca62ef9aaf0af..77f0e6123dfad 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -8,18 +8,15 @@ import React, { useEffect, useState, Fragment, FC, useMemo, useCallback } from 'react'; import { Router } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart } from 'kibana/public'; import { EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiPageContent, EuiPageContentBody, + EuiPageHeader, EuiSpacer, EuiTabbedContent, - EuiText, - EuiTitle, EuiTabbedContentTab, } from '@elastic/eui'; @@ -176,6 +173,19 @@ export const JobsListPage: FC<{ defaultMessage: 'Analytics jobs docs', }); + const docsLink = ( + + {currentTabId === 'anomaly_detection_jobs' ? anomalyDetectionDocsLabel : analyticsDocsLabel} + + ); + function renderTabs() { return ( - + } + description={ + + } + rightSideItems={[docsLink]} + bottomBorder + /> + + + + - - - -

    - {i18n.translate('xpack.ml.management.jobsList.jobsListTitle', { - defaultMessage: 'Machine Learning Jobs', - })} -

    -
    - - - {currentTabId === 'anomaly_detection_jobs' - ? anomalyDetectionDocsLabel - : analyticsDocsLabel} - - -
    -
    - - - - {i18n.translate('xpack.ml.management.jobsList.jobsListTagline', { - defaultMessage: 'View machine learning analytics and anomaly detection jobs.', - })} - - - - - {spacesEnabled && ( - <> - setShowSyncFlyout(true)}> - {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { - defaultMessage: 'Synchronize saved objects', - })} - - {showSyncFlyout && } - - - )} - {renderTabs()} - -
    + {spacesEnabled && ( + <> + setShowSyncFlyout(true)}> + {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { + defaultMessage: 'Synchronize saved objects', + })} + + {showSyncFlyout && } + + + )} + {renderTabs()} +
    diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3cf0891bc73b2..22967ad94a083 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13627,7 +13627,6 @@ "xpack.maps.visTypeAlias.description": "マップを作成し、複数のレイヤーとインデックスを使用して、スタイルを設定します。", "xpack.maps.visTypeAlias.title": "マップ", "xpack.ml.accessDenied.description": "ML プラグインへのアクセスパーミッションがありません", - "xpack.ml.accessDenied.label": "パーミッションがありません", "xpack.ml.accessDeniedLabel": "アクセスが拒否されました", "xpack.ml.accessDeniedTabLabel": "アクセス拒否", "xpack.ml.actions.applyEntityFieldsFiltersTitle": "値でフィルター", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3a1836cc25014..b1846be15ffa9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13805,7 +13805,6 @@ "xpack.maps.visTypeAlias.description": "使用多个图层和索引创建地图并提供样式。", "xpack.maps.visTypeAlias.title": "Maps", "xpack.ml.accessDenied.description": "您无权访问 ML 插件", - "xpack.ml.accessDenied.label": "权限不足", "xpack.ml.accessDeniedLabel": "访问被拒绝", "xpack.ml.accessDeniedTabLabel": "访问被拒绝", "xpack.ml.actions.applyEntityFieldsFiltersTitle": "筛留值", From df8787b104efd9185a15d29bd5682826e5d0ceaa Mon Sep 17 00:00:00 2001 From: Michael Marcialis Date: Fri, 25 Jun 2021 20:58:52 -0400 Subject: [PATCH 29/40] Home & Kibana Overview Page Template Update (#103003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * apply page template comp to kibana overview * apply page template comp to home page * clean up * strip null actions from array * update snapshot and remove outdated import * fix tests and update snapshots * update tests and snapshots * Switch to KibanaPageTemplate; use template=“empty” * update snapshots * make `EuiCard` transparent * updated snapshots * restored data-test-subj="homeApp" * change scope of styles * update snapshots --- .../__snapshots__/home.test.js.snap | 1344 ++++++-------- .../public/application/components/_home.scss | 16 - .../components/_solutions_section.scss | 4 - .../public/application/components/home.js | 99 +- .../application/components/home.test.js | 2 +- .../public/components/_overview.scss | 22 +- .../__snapshots__/add_data.test.tsx.snap | 2 +- .../public/components/add_data/add_data.tsx | 2 +- .../getting_started.test.tsx.snap | 24 +- .../getting_started/getting_started.tsx | 2 +- .../__snapshots__/overview.test.tsx.snap | 1641 +++++++---------- .../components/overview/overview.test.tsx | 2 +- .../public/components/overview/overview.tsx | 261 +-- .../public/overview_page/index.ts | 2 +- .../overview_page_actions.test.tsx.snap | 101 + .../index.ts | 2 +- .../overview_page_actions.test.tsx | 51 + .../overview_page_actions.tsx | 88 + .../overview_page_header.test.tsx.snap | 69 - .../_overview_page_header.scss | 14 - .../overview_page_header/index.scss | 1 - .../overview_page_header.test.tsx | 38 - .../overview_page_header.tsx | 141 -- 23 files changed, 1743 insertions(+), 2185 deletions(-) create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap rename src/plugins/kibana_react/public/overview_page/{overview_page_header => overview_page_actions}/index.ts (89%) create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/_overview_page_header.scss delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/index.scss delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.test.tsx delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.tsx diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap index b949fa7995d30..153438b34eb47 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap @@ -1,819 +1,707 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`home change home route should render a link to change the default route in advanced settings if advanced settings is enabled 1`] = ` -
    - + />, + "rightSideItems": Array [], } - /> -
    + - - - - - - - - -
    -
    + + + + + + + +