diff --git a/.github/workflows/pr-project-assigner.yml b/.github/workflows/pr-project-assigner.yml index 708c9efea404ba4..ccaa5b11aa80c12 100644 --- a/.github/workflows/pr-project-assigner.yml +++ b/.github/workflows/pr-project-assigner.yml @@ -8,7 +8,7 @@ jobs: name: Assign a PR to project based on label steps: - name: Assign to project - uses: elastic/github-actions/project-assigner@v1.0.0 + uses: elastic/github-actions/project-assigner@v1.0.1 id: project_assigner with: issue-mappings: | diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml index aec3bf88f0ee208..737da4f7fe37107 100644 --- a/.github/workflows/project-assigner.yml +++ b/.github/workflows/project-assigner.yml @@ -8,7 +8,7 @@ jobs: name: Assign issue or PR to project based on label steps: - name: Assign to project - uses: elastic/github-actions/project-assigner@v1.0.0 + uses: elastic/github-actions/project-assigner@v1.0.1 id: project_assigner with: issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173895}, {"label": "Feature:Lens", "projectName": "Lens", "columnId": 6219363}, {"label": "Team:Canvas", "projectName": "canvas", "columnId": 6187593}]' diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 79db96d759aa503..44610a2fd342695 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -34,7 +34,7 @@ If the {es} {security-features} are enabled, you must have the {ref}/security-privileges.html[`manage_watcher` or `monitor_watcher`] cluster privileges to use Watcher in {kib}. -Alternately, you can have the built-in `kibana_user` role +Alternately, you can have the built-in `kibana_admin` role and either of these watcher roles: * `watcher_admin`. You can perform all Watcher actions, including create and edit watches. diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index df4d8a0b65ee7fb..146d4e97b6cf4a6 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -80,6 +80,21 @@ specified explicitly. *Impact:* Any workflow that involved manually clearing generated bundles will have to be updated with the new path. +[float] +[[breaking_80_user_role_changes]] +=== User role changes + +[float] +==== `kibana_user` role has been removed and `kibana_admin` has been added. + +*Details:* The `kibana_user` role has been removed and `kibana_admin` has been added to better +reflect its intended use. This role continues to grant all access to every +{kib} feature. If you wish to restrict access to specific features, create +custom roles with {kibana-ref}/kibana-privileges.html[{kib} privileges]. + +*Impact:* Any users currently assigned the `kibana_user` role will need to +instead be assigned the `kibana_admin` role to maintain their current +access level. [float] [[breaking_80_reporting_changes]] diff --git a/docs/uptime-guide/security.asciidoc b/docs/uptime-guide/security.asciidoc index 2a960348b1e02cf..6651b33ea0e0e84 100644 --- a/docs/uptime-guide/security.asciidoc +++ b/docs/uptime-guide/security.asciidoc @@ -42,7 +42,7 @@ PUT /_security/role/uptime === Assign the role to a user Next, you'll need to create a user with both the `uptime` role, and another role with sufficient {kibana-ref}/kibana-privileges.html[Kibana privileges], -such as the `kibana_user` role. +such as the `kibana_admin` role. You can do this with the following request: ["source","sh",subs="attributes,callouts"] @@ -50,7 +50,7 @@ You can do this with the following request: PUT /_security/user/jacknich { "password" : "j@rV1s", - "roles" : [ "uptime", "kibana_user" ], + "roles" : [ "uptime", "kibana_admin" ], "full_name" : "Jack Nicholson", "email" : "jacknich@example.com", "metadata" : { diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index 61bcb9a49c90121..11516e32400fb94 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -63,7 +63,7 @@ remote monitoring cluster, you must use credentials that are valid on both the -- -.. Create users that have the `monitoring_user` and `kibana_user` +.. Create users that have the `monitoring_user` and `kibana_admin` {ref}/built-in-roles.html[built-in roles]. . Open {kib} in your web browser. diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index 2636b3dfc1bd38b..853c735418cea8f 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -2,11 +2,11 @@ [[xpack-security-authorization]] === Granting access to {kib} -The Elastic Stack comes with the `kibana_user` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. +The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. -When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_user` role in addition to a custom role that grants Kibana privileges is ineffective because `kibana_user` has access to all the features in all spaces. +When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants Kibana privileges is ineffective because `kibana_admin` has access to all the features in all spaces. -NOTE: When running multiple tenants of Kibana by changing the `kibana.index` in your `kibana.yml`, you cannot use `kibana_user` to grant access. You must create custom roles that authorize the user for that specific tenant. Although multi-tenant installations are supported, the recommended approach to securing access to Kibana segments is to grant users access to specific spaces. +NOTE: When running multiple tenants of Kibana by changing the `kibana.index` in your `kibana.yml`, you cannot use `kibana_admin` to grant access. You must create custom roles that authorize the user for that specific tenant. Although multi-tenant installations are supported, the recommended approach to securing access to Kibana segments is to grant users access to specific spaces. [role="xpack"] === {kib} role management diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index 5f5d85fe8d3bebe..825580bdc772ed7 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -85,14 +85,14 @@ elasticsearch.username: 'custom_kibana_system' [[reporting-roles-user-api]] ==== With the user API This example uses the {ref}/security-api-put-user.html[user API] to create a user who has the -`reporting_user` role and the `kibana_user` role: +`reporting_user` role and the `kibana_admin` role: [source, sh] --------------------------------------------------------------- POST /_security/user/reporter { "password" : "x-pack-test-password", - "roles" : ["kibana_user", "reporting_user"], + "roles" : ["kibana_admin", "reporting_user"], "full_name" : "Reporting User" } --------------------------------------------------------------- @@ -106,11 +106,11 @@ roles on a per user basis, or assign roles to groups of users. By default, role mappings are configured in {ref}/mapping-roles.html[`config/shield/role_mapping.yml`]. For example, the following snippet assigns the user named Bill Murray the -`kibana_user` and `reporting_user` roles: +`kibana_admin` and `reporting_user` roles: [source,yaml] -------------------------------------------------------------------------------- -kibana_user: +kibana_admin: - "cn=Bill Murray,dc=example,dc=com" reporting_user: - "cn=Bill Murray,dc=example,dc=com" diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index 2d07b57bfabe1ea..b6b5248777f6baa 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -104,7 +104,7 @@ You can manage privileges on the *Management / Security / Roles* page in {kib}. If you're using the native realm with Basic Authentication, you can assign roles using the *Management / Security / Users* page in {kib} or the {ref}/security-api.html#security-user-apis[user management APIs]. For example, -the following creates a user named `jacknich` and assigns it the `kibana_user` +the following creates a user named `jacknich` and assigns it the `kibana_admin` role: [source,js] @@ -112,7 +112,7 @@ role: POST /_security/user/jacknich { "password" : "t0pS3cr3t", - "roles" : [ "kibana_user" ] + "roles" : [ "kibana_admin" ] } -------------------------------------------------------------------------------- // CONSOLE diff --git a/src/plugins/console/server/lib/elasticsearch_proxy_config.ts b/src/plugins/console/server/lib/elasticsearch_proxy_config.ts index 901d726ac51d82e..28a971794d403d9 100644 --- a/src/plugins/console/server/lib/elasticsearch_proxy_config.ts +++ b/src/plugins/console/server/lib/elasticsearch_proxy_config.ts @@ -21,9 +21,10 @@ import _ from 'lodash'; import http from 'http'; import https from 'https'; import url from 'url'; -import { Duration } from 'moment'; -const createAgent = (legacyConfig: any) => { +import { ESConfigForProxy } from '../types'; + +const createAgent = (legacyConfig: ESConfigForProxy) => { const target = url.parse(_.head(legacyConfig.hosts)); if (!/^https/.test(target.protocol || '')) return new http.Agent(); @@ -59,7 +60,7 @@ const createAgent = (legacyConfig: any) => { return new https.Agent(agentOptions); }; -export const getElasticsearchProxyConfig = (legacyConfig: { requestTimeout: Duration }) => { +export const getElasticsearchProxyConfig = (legacyConfig: ESConfigForProxy) => { return { timeout: legacyConfig.requestTimeout.asMilliseconds(), agent: createAgent(legacyConfig), diff --git a/src/plugins/console/server/plugin.ts b/src/plugins/console/server/plugin.ts index c8ef84aee3b6158..65647bd5acb7c5e 100644 --- a/src/plugins/console/server/plugin.ts +++ b/src/plugins/console/server/plugin.ts @@ -60,9 +60,7 @@ export class ConsoleServerPlugin implements Plugin { const legacyConfig = readLegacyEsConfig(); return { ...elasticsearch, - hosts: legacyConfig.hosts, - requestHeadersWhitelist: legacyConfig.requestHeadersWhitelist, - customHeaders: legacyConfig.customHeaders, + ...legacyConfig, }; }, pathFilters: proxyPathFilters, diff --git a/src/plugins/console/server/types.ts b/src/plugins/console/server/types.ts index 60ce56ad39fcd48..adafcd4d305269c 100644 --- a/src/plugins/console/server/types.ts +++ b/src/plugins/console/server/types.ts @@ -31,4 +31,12 @@ export interface ESConfigForProxy { requestHeadersWhitelist: string[]; customHeaders: Record; requestTimeout: Duration; + ssl?: { + verificationMode: 'none' | 'certificate' | 'full'; + certificateAuthorities: string[] | string; + alwaysPresentCertificate: boolean; + certificate?: string; + key?: string; + keyPassphrase?: string; + }; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx new file mode 100644 index 000000000000000..378ad9509c2170a --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiTitle +} from '@elastic/eui'; +import cytoscape from 'cytoscape'; +import React from 'react'; +import { Buttons } from './Buttons'; +import { Info } from './Info'; +import { ServiceMetricList } from './ServiceMetricList'; + +const popoverMinWidth = 280; + +interface ContentsProps { + focusedServiceName?: string; + isService: boolean; + label: string; + onFocusClick: () => void; + selectedNodeData: cytoscape.NodeDataDefinition; + selectedNodeServiceName: string; +} + +export function Contents({ + selectedNodeData, + focusedServiceName, + isService, + label, + onFocusClick, + selectedNodeServiceName +}: ContentsProps) { + return ( + + + +

{label}

+
+ +
+ + {isService ? ( + + ) : ( + + )} + + {isService && ( + + )} +
+ ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx index 1c5443e404f9b32..d432119505382a3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import cytoscape from 'cytoscape'; +import React from 'react'; import styled from 'styled-components'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; const ItemRow = styled.div` line-height: 2; @@ -19,8 +20,8 @@ const ItemTitle = styled.dt` const ItemDescription = styled.dd``; -interface InfoProps { - type: string; +interface InfoProps extends cytoscape.NodeDataDefinition { + type?: string; subtype?: string; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx new file mode 100644 index 000000000000000..b26488c5ef7de9a --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { + ApmPluginContext, + ApmPluginContextValue +} from '../../../../context/ApmPluginContext'; +import { Contents } from './Contents'; + +const selectedNodeData = { + id: 'opbeans-node', + label: 'opbeans-node', + href: + '#/services/opbeans-node/service-map?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', + agentName: 'nodejs', + type: 'service' +}; + +storiesOf('app/ServiceMap/Popover/Contents', module).add( + 'example', + () => { + return ( + + {}} + selectedNodeServiceName="opbeans-node" + /> + + ); + }, + { + info: { + propTablesExclude: [ApmPluginContext.Provider], + source: false + } + } +); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index dfb78aaa0214c45..e8e37cfdfb1f0f4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -4,27 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiPopover, - EuiTitle -} from '@elastic/eui'; +import { EuiPopover } from '@elastic/eui'; import cytoscape from 'cytoscape'; import React, { CSSProperties, + useCallback, useContext, useEffect, - useState, - useCallback + useRef, + useState } from 'react'; import { CytoscapeContext } from '../Cytoscape'; -import { Buttons } from './Buttons'; -import { Info } from './Info'; -import { ServiceMetricList } from './ServiceMetricList'; - -const popoverMinWidth = 280; +import { Contents } from './Contents'; interface PopoverProps { focusedServiceName?: string; @@ -35,56 +26,62 @@ export function Popover({ focusedServiceName }: PopoverProps) { const [selectedNode, setSelectedNode] = useState< cytoscape.NodeSingular | undefined >(undefined); - const onFocusClick = useCallback(() => setSelectedNode(undefined), [ + const deselect = useCallback(() => setSelectedNode(undefined), [ setSelectedNode ]); - - useEffect(() => { - const selectHandler: cytoscape.EventHandler = event => { - setSelectedNode(event.target); - }; - const unselectHandler: cytoscape.EventHandler = () => { - setSelectedNode(undefined); - }; - - if (cy) { - cy.on('select', 'node', selectHandler); - cy.on('unselect', 'node', unselectHandler); - cy.on('data viewport', unselectHandler); - } - - return () => { - if (cy) { - cy.removeListener('select', 'node', selectHandler); - cy.removeListener('unselect', 'node', unselectHandler); - cy.removeListener('data viewport', undefined, unselectHandler); - } - }; - }, [cy]); - const renderedHeight = selectedNode?.renderedHeight() ?? 0; const renderedWidth = selectedNode?.renderedWidth() ?? 0; const { x, y } = selectedNode?.renderedPosition() ?? { x: 0, y: 0 }; const isOpen = !!selectedNode; - const selectedNodeServiceName: string = selectedNode?.data('id'); const isService = selectedNode?.data('type') === 'service'; const triggerStyle: CSSProperties = { background: 'transparent', height: renderedHeight, position: 'absolute', - width: renderedWidth + width: renderedWidth, + border: '3px dotted red' }; - const trigger =
; - + const trigger =
; const zoom = cy?.zoom() ?? 1; const height = selectedNode?.height() ?? 0; - const translateY = y - (zoom + 1) * (height / 2); + const translateY = y - ((zoom + 1) * height) / 4; const popoverStyle: CSSProperties = { position: 'absolute', transform: `translate(${x}px, ${translateY}px)` }; - const data = selectedNode?.data() ?? {}; - const label = data.label || selectedNodeServiceName; + const selectedNodeData = selectedNode?.data() ?? {}; + const selectedNodeServiceName = selectedNodeData.id; + const label = selectedNodeData.label || selectedNodeServiceName; + const popoverRef = useRef(null); + + // Set up Cytoscape event handlers + useEffect(() => { + const selectHandler: cytoscape.EventHandler = event => { + setSelectedNode(event.target); + }; + + if (cy) { + cy.on('select', 'node', selectHandler); + cy.on('unselect', 'node', deselect); + cy.on('data viewport', deselect); + } + + return () => { + if (cy) { + cy.removeListener('select', 'node', selectHandler); + cy.removeListener('unselect', 'node', deselect); + cy.removeListener('data viewport', undefined, deselect); + } + }; + }, [cy, deselect]); + + // Handle positioning of popover. This makes it so the popover positions + // itself correctly and the arrows are always pointing to where they should. + useEffect(() => { + if (popoverRef.current) { + popoverRef.current.positionPopoverFluid(); + } + }, [popoverRef, x, y]); return ( {}} isOpen={isOpen} + ref={popoverRef} style={popoverStyle} > - - - -

{label}

-
- -
- - - {isService ? ( - - ) : ( - - )} - - {isService && ( - - )} -
+
); } diff --git a/x-pack/legacy/plugins/canvas/server/plugin.ts b/x-pack/legacy/plugins/canvas/server/plugin.ts index ac3edbabce930e3..1f17e85bfd294f2 100644 --- a/x-pack/legacy/plugins/canvas/server/plugin.ts +++ b/x-pack/legacy/plugins/canvas/server/plugin.ts @@ -5,14 +5,11 @@ */ import { CoreSetup, PluginsSetup } from './shim'; -import { routes } from './routes'; import { functions } from '../canvas_plugin_src/functions/server'; import { loadSampleData } from './sample_data'; export class Plugin { public setup(core: CoreSetup, plugins: PluginsSetup) { - routes(core); - plugins.interpreter.register({ serverFunctions: functions }); core.injectUiAppVars('canvas', async () => { diff --git a/x-pack/legacy/plugins/canvas/server/routes/index.ts b/x-pack/legacy/plugins/canvas/server/routes/index.ts deleted file mode 100644 index 6898a3c459e3d47..000000000000000 --- a/x-pack/legacy/plugins/canvas/server/routes/index.ts +++ /dev/null @@ -1,12 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shareableWorkpads } from './shareables'; -import { CoreSetup } from '../shim'; - -export function routes(setup: CoreSetup): void { - shareableWorkpads(setup.http.route); -} diff --git a/x-pack/legacy/plugins/canvas/server/routes/shareables.ts b/x-pack/legacy/plugins/canvas/server/routes/shareables.ts deleted file mode 100644 index e8186ceceb47f27..000000000000000 --- a/x-pack/legacy/plugins/canvas/server/routes/shareables.ts +++ /dev/null @@ -1,72 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import archiver from 'archiver'; - -import { - API_ROUTE_SHAREABLE_RUNTIME, - API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, - API_ROUTE_SHAREABLE_ZIP, -} from '../../common/lib/constants'; - -import { - SHAREABLE_RUNTIME_FILE, - SHAREABLE_RUNTIME_NAME, - SHAREABLE_RUNTIME_SRC, -} from '../../shareable_runtime/constants'; - -import { CoreSetup } from '../shim'; - -export function shareableWorkpads(route: CoreSetup['http']['route']) { - // get runtime - route({ - method: 'GET', - path: API_ROUTE_SHAREABLE_RUNTIME, - - handler: { - file: { - path: SHAREABLE_RUNTIME_FILE, - // The option setting is not for typical use. We're using it here to avoid - // problems in Cloud environments. See elastic/kibana#47405. - confine: false, - }, - }, - }); - - // download runtime - route({ - method: 'GET', - path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, - - handler(_request, handler) { - // The option setting is not for typical use. We're using it here to avoid - // problems in Cloud environments. See elastic/kibana#47405. - // @ts-ignore No type for inert Hapi handler - const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false }); - file.type('application/octet-stream'); - return file; - }, - }); - - route({ - method: 'POST', - path: API_ROUTE_SHAREABLE_ZIP, - handler(request, handler) { - const workpad = request.payload; - - const archive = archiver('zip'); - archive.append(JSON.stringify(workpad), { name: 'workpad.json' }); - archive.file(`${SHAREABLE_RUNTIME_SRC}/template.html`, { name: 'index.html' }); - archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` }); - - const response = handler.response(archive); - response.header('content-type', 'application/zip'); - archive.finalize(); - - return response; - }, - }); -} diff --git a/x-pack/legacy/plugins/encrypted_saved_objects/index.ts b/x-pack/legacy/plugins/encrypted_saved_objects/index.ts index 85aa5c22135b6cb..69058a7a33f59fd 100644 --- a/x-pack/legacy/plugins/encrypted_saved_objects/index.ts +++ b/x-pack/legacy/plugins/encrypted_saved_objects/index.ts @@ -21,7 +21,9 @@ export const encryptedSavedObjects = (kibana: { // Some legacy plugins still use `enabled` config key, so we keep it here, but the rest of the // keys is handled by the New Platform plugin. config: (Joi: Root) => - Joi.object({ enabled: Joi.boolean().default(true) }) + Joi.object({ + enabled: Joi.boolean().default(true), + }) .unknown(true) .default(), diff --git a/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.html b/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.html index 8c67451b86f36e3..63cd4440ecf8a20 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.html +++ b/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.html @@ -15,9 +15,9 @@ class="kuiInfoPanelBody__message" i18n-id="xpack.monitoring.accessDenied.notAuthorizedDescription" i18n-default-message="You are not authorized to access Monitoring. To use Monitoring, you - need the privileges granted by both the `{kibanaUser}` and + need the privileges granted by both the `{kibanaAdmin}` and `{monitoringUser}` roles." - i18n-values="{ kibanaUser: 'kibana_user', monitoringUser: 'monitoring_user' }" + i18n-values="{ kibanaAdmin: 'kibana_admin', monitoringUser: 'monitoring_user' }" >
clusterStub, + }, + }; + beforeAll(async function() { const crypto = nodeCrypto({ encryptionKey }); encryptedHeaders = await crypto.encrypt(headers); @@ -55,11 +61,11 @@ describe('CSV Execute Job', function() { _scroll_id: 'defaultScrollId', }; clusterStub = { - callWithRequest: function() {}, + callAsCurrentUser: function() {}, }; - callWithRequestStub = sinon - .stub(clusterStub, 'callWithRequest') + callAsCurrentUserStub = sinon + .stub(clusterStub, 'callAsCurrentUser') .resolves(defaultElasticsearchResponse); const configGetStub = sinon.stub(); @@ -68,7 +74,6 @@ describe('CSV Execute Job', function() { uiSettingsGetStub.withArgs('csv:quoteValues').returns(true); mockServer = { - expose: function() {}, fieldFormatServiceFactory: function() { const uiConfigMock = {}; uiConfigMock['format:defaultTypeMap'] = { @@ -81,13 +86,6 @@ describe('CSV Execute Job', function() { return fieldFormatsRegistry; }, - plugins: { - elasticsearch: { - getCluster: function() { - return clusterStub; - }, - }, - }, config: function() { return { get: configGetStub, @@ -117,7 +115,7 @@ describe('CSV Execute Job', function() { describe('calls getScopedSavedObjectsClient with request', function() { it('containing decrypted headers', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -135,7 +133,7 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('server.basePath') .returns(serverBasePath); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -153,7 +151,7 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('server.basePath') .returns(serverBasePath); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobBasePath = 'foo-job/basePath/'; await executeJob( 'job789', @@ -176,7 +174,7 @@ describe('CSV Execute Job', function() { it('passed scoped SavedObjectsClient to uiSettingsServiceFactory', async function() { const returnValue = Symbol(); mockServer.savedObjects.getScopedSavedObjectsClient.returns(returnValue); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -190,15 +188,15 @@ describe('CSV Execute Job', function() { }); describe('basic Elasticsearch call behavior', function() { - it('should decrypt encrypted headers and pass to callWithRequest', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + it('should decrypt encrypted headers and pass to callAsCurrentUser', async function() { + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, cancellationToken ); - expect(callWithRequestStub.called).toBe(true); - expect(callWithRequestStub.firstCall.args[0].headers).toEqual(headers); + expect(callAsCurrentUserStub.called).toBe(true); + expect(callAsCurrentUserStub.firstCall.args[0]).toEqual('search'); }); it('should pass the index and body to execute the initial search', async function() { @@ -207,7 +205,7 @@ describe('CSV Execute Job', function() { testBody: true, }; - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const job = { headers: encryptedHeaders, fields: [], @@ -219,115 +217,115 @@ describe('CSV Execute Job', function() { await executeJob('job777', job, cancellationToken); - const searchCall = callWithRequestStub.firstCall; - expect(searchCall.args[1]).toBe('search'); - expect(searchCall.args[2].index).toBe(index); - expect(searchCall.args[2].body).toBe(body); + const searchCall = callAsCurrentUserStub.firstCall; + expect(searchCall.args[0]).toBe('search'); + expect(searchCall.args[1].index).toBe(index); + expect(searchCall.args[1].body).toBe(body); }); it('should pass the scrollId from the initial search to the subsequent scroll', async function() { const scrollId = getRandomScrollId(); - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: scrollId, }); - callWithRequestStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = executeJobFactory(mockServer, mockLogger); + callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, cancellationToken ); - const scrollCall = callWithRequestStub.secondCall; + const scrollCall = callAsCurrentUserStub.secondCall; - expect(scrollCall.args[1]).toBe('scroll'); - expect(scrollCall.args[2].scrollId).toBe(scrollId); + expect(scrollCall.args[0]).toBe('scroll'); + expect(scrollCall.args[1].scrollId).toBe(scrollId); }); it('should not execute scroll if there are no hits from the search', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, cancellationToken ); - expect(callWithRequestStub.callCount).toBe(2); + expect(callAsCurrentUserStub.callCount).toBe(2); - const searchCall = callWithRequestStub.firstCall; - expect(searchCall.args[1]).toBe('search'); + const searchCall = callAsCurrentUserStub.firstCall; + expect(searchCall.args[0]).toBe('search'); - const clearScrollCall = callWithRequestStub.secondCall; - expect(clearScrollCall.args[1]).toBe('clearScroll'); + const clearScrollCall = callAsCurrentUserStub.secondCall; + expect(clearScrollCall.args[0]).toBe('clearScroll'); }); it('should stop executing scroll if there are no hits', async function() { - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().resolves({ + callAsCurrentUserStub.onSecondCall().resolves({ hits: { hits: [], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, cancellationToken ); - expect(callWithRequestStub.callCount).toBe(3); + expect(callAsCurrentUserStub.callCount).toBe(3); - const searchCall = callWithRequestStub.firstCall; - expect(searchCall.args[1]).toBe('search'); + const searchCall = callAsCurrentUserStub.firstCall; + expect(searchCall.args[0]).toBe('search'); - const scrollCall = callWithRequestStub.secondCall; - expect(scrollCall.args[1]).toBe('scroll'); + const scrollCall = callAsCurrentUserStub.secondCall; + expect(scrollCall.args[0]).toBe('scroll'); - const clearScroll = callWithRequestStub.thirdCall; - expect(clearScroll.args[1]).toBe('clearScroll'); + const clearScroll = callAsCurrentUserStub.thirdCall; + expect(clearScroll.args[0]).toBe('clearScroll'); }); it('should call clearScroll with scrollId when there are no more hits', async function() { const lastScrollId = getRandomScrollId(); - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().resolves({ + callAsCurrentUserStub.onSecondCall().resolves({ hits: { hits: [], }, _scroll_id: lastScrollId, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); await executeJob( 'job456', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, cancellationToken ); - const lastCall = callWithRequestStub.getCall(callWithRequestStub.callCount - 1); - expect(lastCall.args[1]).toBe('clearScroll'); - expect(lastCall.args[2].scrollId).toEqual([lastScrollId]); + const lastCall = callAsCurrentUserStub.getCall(callAsCurrentUserStub.callCount - 1); + expect(lastCall.args[0]).toBe('clearScroll'); + expect(lastCall.args[1].scrollId).toEqual([lastScrollId]); }); it('calls clearScroll when there is an error iterating the hits', async function() { const lastScrollId = getRandomScrollId(); - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [ { @@ -341,7 +339,7 @@ describe('CSV Execute Job', function() { _scroll_id: lastScrollId, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -352,9 +350,9 @@ describe('CSV Execute Job', function() { executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot(`[TypeError: Cannot read property 'indexOf' of undefined]`); - const lastCall = callWithRequestStub.getCall(callWithRequestStub.callCount - 1); - expect(lastCall.args[1]).toBe('clearScroll'); - expect(lastCall.args[2].scrollId).toEqual([lastScrollId]); + const lastCall = callAsCurrentUserStub.getCall(callAsCurrentUserStub.callCount - 1); + expect(lastCall.args[0]).toBe('clearScroll'); + expect(lastCall.args[1].scrollId).toEqual([lastScrollId]); }); }); @@ -364,14 +362,14 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('xpack.reporting.csv.checkForFormulas') .returns(true); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { one: '=SUM(A1:A2)', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -392,14 +390,14 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('xpack.reporting.csv.checkForFormulas') .returns(true); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { '=SUM(A1:A2)': 'foo', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], @@ -420,14 +418,14 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('xpack.reporting.csv.checkForFormulas') .returns(true); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -448,14 +446,14 @@ describe('CSV Execute Job', function() { .config() .get.withArgs('xpack.reporting.csv.checkForFormulas') .returns(false); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { one: '=SUM(A1:A2)', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -474,8 +472,8 @@ describe('CSV Execute Job', function() { describe('Elasticsearch call errors', function() { it('should reject Promise if search call errors out', async function() { - callWithRequestStub.rejects(new Error()); - const executeJob = executeJobFactory(mockServer, mockLogger); + callAsCurrentUserStub.rejects(new Error()); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -487,14 +485,14 @@ describe('CSV Execute Job', function() { }); it('should reject Promise if scroll call errors out', async function() { - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().rejects(new Error()); - const executeJob = executeJobFactory(mockServer, mockLogger); + callAsCurrentUserStub.onSecondCall().rejects(new Error()); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -508,14 +506,14 @@ describe('CSV Execute Job', function() { describe('invalid responses', function() { it('should reject Promise if search returns hits but no _scroll_id', async function() { - callWithRequestStub.resolves({ + callAsCurrentUserStub.resolves({ hits: { hits: [{}], }, _scroll_id: undefined, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -529,14 +527,14 @@ describe('CSV Execute Job', function() { }); it('should reject Promise if search returns no hits and no _scroll_id', async function() { - callWithRequestStub.resolves({ + callAsCurrentUserStub.resolves({ hits: { hits: [], }, _scroll_id: undefined, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -550,21 +548,21 @@ describe('CSV Execute Job', function() { }); it('should reject Promise if scroll returns hits but no _scroll_id', async function() { - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().resolves({ + callAsCurrentUserStub.onSecondCall().resolves({ hits: { hits: [{}], }, _scroll_id: undefined, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -578,21 +576,21 @@ describe('CSV Execute Job', function() { }); it('should reject Promise if scroll returns no hits and no _scroll_id', async function() { - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().resolves({ + callAsCurrentUserStub.onSecondCall().resolves({ hits: { hits: [], }, _scroll_id: undefined, }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: [], @@ -610,23 +608,25 @@ describe('CSV Execute Job', function() { const scrollId = getRandomScrollId(); beforeEach(function() { - // We have to "re-stub" the callWithRequest stub here so that we can use the fakeFunction + // We have to "re-stub" the callAsCurrentUser stub here so that we can use the fakeFunction // that delays the Promise resolution so we have a chance to call cancellationToken.cancel(). // Otherwise, we get into an endless loop, and don't have a chance to call cancel - callWithRequestStub.restore(); - callWithRequestStub = sinon.stub(clusterStub, 'callWithRequest').callsFake(async function() { - await delay(1); - return { - hits: { - hits: [{}], - }, - _scroll_id: scrollId, - }; - }); + callAsCurrentUserStub.restore(); + callAsCurrentUserStub = sinon + .stub(clusterStub, 'callAsCurrentUser') + .callsFake(async function() { + await delay(1); + return { + hits: { + hits: [{}], + }, + _scroll_id: scrollId, + }; + }); }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); executeJob( 'job345', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -634,14 +634,14 @@ describe('CSV Execute Job', function() { ); await delay(250); - const callCount = callWithRequestStub.callCount; + const callCount = callAsCurrentUserStub.callCount; cancellationToken.cancel(); await delay(250); - expect(callWithRequestStub.callCount).toBe(callCount + 1); // last call is to clear the scroll + expect(callAsCurrentUserStub.callCount).toBe(callCount + 1); // last call is to clear the scroll }); it(`shouldn't call clearScroll if it never got a scrollId`, async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); executeJob( 'job345', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -649,13 +649,13 @@ describe('CSV Execute Job', function() { ); cancellationToken.cancel(); - for (let i = 0; i < callWithRequestStub.callCount; ++i) { - expect(callWithRequestStub.getCall(i).args[1]).to.not.be('clearScroll'); + for (let i = 0; i < callAsCurrentUserStub.callCount; ++i) { + expect(callAsCurrentUserStub.getCall(i).args[1]).to.not.be('clearScroll'); } }); it('should call clearScroll if it got a scrollId', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); executeJob( 'job345', { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, @@ -665,15 +665,15 @@ describe('CSV Execute Job', function() { cancellationToken.cancel(); await delay(100); - const lastCall = callWithRequestStub.getCall(callWithRequestStub.callCount - 1); - expect(lastCall.args[1]).toBe('clearScroll'); - expect(lastCall.args[2].scrollId).toEqual([scrollId]); + const lastCall = callAsCurrentUserStub.getCall(callAsCurrentUserStub.callCount - 1); + expect(lastCall.args[0]).toBe('clearScroll'); + expect(lastCall.args[1].scrollId).toEqual([scrollId]); }); }); describe('csv content', function() { it('should write column headers to output, even if there are no results', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -685,7 +685,7 @@ describe('CSV Execute Job', function() { it('should use custom uiSettings csv:separator for header', async function() { uiSettingsGetStub.withArgs('csv:separator').returns(';'); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -697,7 +697,7 @@ describe('CSV Execute Job', function() { it('should escape column headers if uiSettings csv:quoteValues is true', async function() { uiSettingsGetStub.withArgs('csv:quoteValues').returns(true); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -709,7 +709,7 @@ describe('CSV Execute Job', function() { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function() { uiSettingsGetStub.withArgs('csv:quoteValues').returns(false); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], @@ -720,8 +720,8 @@ describe('CSV Execute Job', function() { }); it('should write column headers to output, when there are results', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); - callWithRequestStub.onFirstCall().resolves({ + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], }, @@ -740,8 +740,8 @@ describe('CSV Execute Job', function() { }); it('should use comma separated values of non-nested fields from _source', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); - callWithRequestStub.onFirstCall().resolves({ + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, @@ -761,14 +761,14 @@ describe('CSV Execute Job', function() { }); it('should concatenate the hits from multiple responses', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); - callWithRequestStub.onFirstCall().resolves({ + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - callWithRequestStub.onSecondCall().resolves({ + callAsCurrentUserStub.onSecondCall().resolves({ hits: { hits: [{ _source: { one: 'baz', two: 'qux' } }], }, @@ -789,8 +789,8 @@ describe('CSV Execute Job', function() { }); it('should use field formatters to format fields', async function() { - const executeJob = executeJobFactory(mockServer, mockLogger); - callWithRequestStub.onFirstCall().resolves({ + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, @@ -834,7 +834,7 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.maxSizeBytes') .returns(1); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -867,7 +867,7 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.maxSizeBytes') .returns(9); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -900,14 +900,14 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.maxSizeBytes') .returns(9); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -941,14 +941,14 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.maxSizeBytes') .returns(18); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -981,14 +981,14 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.scroll') .returns({ duration: scrollDuration }); - callWithRequestStub.onFirstCall().returns({ + callAsCurrentUserStub.onFirstCall().returns({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -998,9 +998,9 @@ describe('CSV Execute Job', function() { await executeJob('job123', jobParams, cancellationToken); - const searchCall = callWithRequestStub.firstCall; - expect(searchCall.args[1]).toBe('search'); - expect(searchCall.args[2].scroll).toBe(scrollDuration); + const searchCall = callAsCurrentUserStub.firstCall; + expect(searchCall.args[0]).toBe('search'); + expect(searchCall.args[1].scroll).toBe(scrollDuration); }); it('passes scroll size to initial search call', async function() { @@ -1010,14 +1010,14 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.scroll') .returns({ size: scrollSize }); - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -1027,9 +1027,9 @@ describe('CSV Execute Job', function() { await executeJob('job123', jobParams, cancellationToken); - const searchCall = callWithRequestStub.firstCall; - expect(searchCall.args[1]).toBe('search'); - expect(searchCall.args[2].size).toBe(scrollSize); + const searchCall = callAsCurrentUserStub.firstCall; + expect(searchCall.args[0]).toBe('search'); + expect(searchCall.args[1].size).toBe(scrollSize); }); it('passes scroll duration to subsequent scroll call', async function() { @@ -1039,14 +1039,14 @@ describe('CSV Execute Job', function() { .get.withArgs('xpack.reporting.csv.scroll') .returns({ duration: scrollDuration }); - callWithRequestStub.onFirstCall().resolves({ + callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{}], }, _scroll_id: 'scrollId', }); - const executeJob = executeJobFactory(mockServer, mockLogger); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, mockLogger); const jobParams = { headers: encryptedHeaders, fields: ['one', 'two'], @@ -1056,9 +1056,9 @@ describe('CSV Execute Job', function() { await executeJob('job123', jobParams, cancellationToken); - const scrollCall = callWithRequestStub.secondCall; - expect(scrollCall.args[1]).toBe('scroll'); - expect(scrollCall.args[2].scroll).toBe(scrollDuration); + const scrollCall = callAsCurrentUserStub.secondCall; + expect(scrollCall.args[0]).toBe('scroll'); + expect(scrollCall.args[1].scroll).toBe(scrollDuration); }); }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index fe64fdc96d9043a..280bbf13fa9928c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import Hapi from 'hapi'; import { i18n } from '@kbn/i18n'; -import { KibanaRequest } from '../../../../../../../src/core/server'; +import { ElasticsearchServiceSetup, KibanaRequest } from '../../../../../../../src/core/server'; import { CSV_JOB_TYPE } from '../../../common/constants'; import { cryptoFactory } from '../../../server/lib'; import { ESQueueWorkerExecuteFn, ExecuteJobFactory, Logger, ServerFacade } from '../../../types'; @@ -15,8 +16,11 @@ import { createGenerateCsv } from './lib/generate_csv'; export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade, parentLogger: Logger) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); +>> = function executeJobFactoryFn( + server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, + parentLogger: Logger +) { const crypto = cryptoFactory(server); const config = server.config(); const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']); @@ -74,8 +78,11 @@ export const executeJobFactory: ExecuteJobFactory { - return callWithRequest(fakeRequest, endpoint, clientParams, options); + return callAsCurrentUser(endpoint, clientParams, options); }; const savedObjects = server.savedObjects; const savedObjectsClient = savedObjects.getScopedSavedObjectsClient( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index a270e3e0329fe89..ddef2aa0a626887 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -6,24 +6,25 @@ import { notFound, notImplemented } from 'boom'; import { get } from 'lodash'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; import { cryptoFactory } from '../../../../server/lib'; import { CreateJobFactory, ImmediateCreateJobFn, - ServerFacade, - RequestFacade, Logger, + RequestFacade, + ServerFacade, } from '../../../../types'; import { + JobDocPayloadPanelCsv, + JobParamsPanelCsv, SavedObject, SavedObjectServiceError, SavedSearchObjectAttributesJSON, SearchPanel, TimeRangeParams, VisObjectAttributesJSON, - JobDocPayloadPanelCsv, - JobParamsPanelCsv, } from '../../types'; import { createJobSearch } from './create_job_search'; @@ -35,7 +36,11 @@ interface VisData { export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(server: ServerFacade, parentLogger: Logger) { +>> = function createJobFactoryFn( + server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, + parentLogger: Logger +) { const crypto = cryptoFactory(server); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index 03f491deaa43d60..b1b7b7d818200e8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; import { cryptoFactory } from '../../../server/lib'; import { @@ -21,7 +22,11 @@ import { createGenerateCsv } from './lib'; export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade, parentLogger: Logger) { +>> = function executeJobFactoryFn( + server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, + parentLogger: Logger +) { const crypto = cryptoFactory(server); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); @@ -85,6 +90,7 @@ export const executeJobFactory: ExecuteJobFactory { export async function generateCsvSearch( req: RequestFacade, server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: Logger, searchPanel: SearchPanel, jobParams: JobParamsDiscoverCsv @@ -152,8 +152,11 @@ export async function generateCsvSearch( sort: sortConfig, }, }; - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const callCluster = (...params: [string, object]) => callWithRequest(req, ...params); + + const { callAsCurrentUser } = elasticsearch.dataClient.asScoped( + KibanaRequest.from(req.getRawRequest()) + ); + const callCluster = (...params: [string, object]) => callAsCurrentUser(...params); const config = server.config(); const uiSettings = await getUiSettings(uiConfig); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js index 4f02ab5d4c077e7..bb33ef9c19a1dde 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js @@ -32,15 +32,6 @@ beforeEach(() => { info: { protocol: 'http', }, - plugins: { - elasticsearch: { - getCluster: memoize(() => { - return { - callWithRequest: jest.fn(), - }; - }), - }, - }, savedObjects: { getScopedSavedObjectsClient: jest.fn(), }, @@ -57,6 +48,12 @@ beforeEach(() => { afterEach(() => generatePngObservableFactory.mockReset()); +const mockElasticsearch = { + dataClient: { + asScoped: () => ({ callAsCurrentUser: jest.fn() }), + }, +}; + const getMockLogger = () => new LevelLogger(); const encryptHeaders = async headers => { @@ -70,7 +67,9 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const browserTimezone = 'UTC'; await executeJob( 'pngJobId', @@ -88,7 +87,9 @@ test(`passes browserTimezone to generatePng`, async () => { }); test(`returns content_type of application/png`, async () => { - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = generatePngObservableFactory(); @@ -108,7 +109,9 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pngJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 7d5c69655c362f0..c9f370197da662a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -5,6 +5,7 @@ */ import * as Rx from 'rxjs'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PNG_JOB_TYPE } from '../../../../common/constants'; import { @@ -27,6 +28,7 @@ type QueuedPngExecutorFactory = ExecuteJobFactory { info: { protocol: 'http', }, - plugins: { - elasticsearch: { - getCluster: memoize(() => { - return { - callWithRequest: jest.fn(), - }; - }), - }, - }, savedObjects: { getScopedSavedObjectsClient: jest.fn(), }, @@ -58,6 +49,11 @@ beforeEach(() => { afterEach(() => generatePdfObservableFactory.mockReset()); const getMockLogger = () => new LevelLogger(); +const mockElasticsearch = { + dataClient: { + asScoped: () => ({ callAsCurrentUser: jest.fn() }), + }, +}; const encryptHeaders = async headers => { const crypto = cryptoFactory(mockServer); @@ -70,7 +66,9 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', @@ -91,7 +89,9 @@ test(`passes browserTimezone to generatePdf`, async () => { }); test(`returns content_type of application/pdf`, async () => { - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = generatePdfObservableFactory(); @@ -111,7 +111,9 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer, getMockLogger(), { browserDriverFactory: {} }); + const executeJob = executeJobFactory(mockServer, mockElasticsearch, getMockLogger(), { + browserDriverFactory: {}, + }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index dee53697c6681d7..162376e31216e01 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -5,6 +5,7 @@ */ import * as Rx from 'rxjs'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { ServerFacade, @@ -28,6 +29,7 @@ type QueuedPdfExecutorFactory = ExecuteJobFactory { @@ -74,10 +69,6 @@ export const reporting = (kibana: any) => { async init(server: Legacy.Server) { const coreSetup = server.newPlatform.setup.core; - const pluginsSetup: ReportingSetupDeps = { - security: server.newPlatform.setup.plugins.security as SecurityPluginSetup, - usageCollection: server.newPlatform.setup.plugins.usageCollection, - }; const fieldFormatServiceFactory = async (uiSettings: IUiSettingsClient) => { const [, plugins] = await coreSetup.getStartServices(); @@ -90,18 +81,22 @@ export const reporting = (kibana: any) => { config: server.config, info: server.info, route: server.route.bind(server), - plugins: { - elasticsearch: server.plugins.elasticsearch, - xpack_main: server.plugins.xpack_main, - }, + plugins: { xpack_main: server.plugins.xpack_main }, savedObjects: server.savedObjects, fieldFormatServiceFactory, uiSettingsServiceFactory: server.uiSettingsServiceFactory, }; - const initializerContext = server.newPlatform.coreContext; - const plugin: ReportingPlugin = reportingPluginFactory(initializerContext, __LEGACY, this); - await plugin.setup(coreSetup, pluginsSetup); + const plugin: ReportingPlugin = reportingPluginFactory( + server.newPlatform.coreContext, + __LEGACY, + this + ); + await plugin.setup(coreSetup, { + elasticsearch: coreSetup.elasticsearch, + security: server.newPlatform.setup.plugins.security as SecurityPluginSetup, + usageCollection: server.newPlatform.setup.plugins.usageCollection, + }); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 05b760c0c3bd6c9..c4e32b3ebcd99e2 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ElasticsearchServiceSetup } from 'kibana/server'; import { ServerFacade, ExportTypesRegistry, @@ -23,6 +24,7 @@ interface CreateQueueFactoryOpts { export function createQueueFactory( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: Logger, { exportTypesRegistry, browserDriverFactory }: CreateQueueFactoryOpts ): Esqueue { @@ -33,7 +35,7 @@ export function createQueueFactory( interval: queueConfig.indexInterval, timeout: queueConfig.timeout, dateSeparator: '.', - client: server.plugins.elasticsearch.getCluster('admin'), + client: elasticsearch.dataClient, logger: createTaggedLogger(logger, ['esqueue', 'queue-worker']), }; @@ -41,7 +43,7 @@ export function createQueueFactory( if (queueConfig.pollEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(server, logger, { + const createWorker = createWorkerFactory(server, elasticsearch, logger, { exportTypesRegistry, browserDriverFactory, }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index 6a5c93db32376a9..f5c42e5505cd1d0 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -5,13 +5,14 @@ */ import * as sinon from 'sinon'; -import { ServerFacade, HeadlessChromiumDriverFactory } from '../../types'; -import { ExportTypesRegistry } from './export_types_registry'; +import { ElasticsearchServiceSetup } from 'kibana/server'; +import { HeadlessChromiumDriverFactory, ServerFacade } from '../../types'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; // @ts-ignore import { ClientMock } from './esqueue/__tests__/fixtures/legacy_elasticsearch'; +import { ExportTypesRegistry } from './export_types_registry'; const configGetStub = sinon.stub(); configGetStub.withArgs('xpack.reporting.queue').returns({ @@ -48,10 +49,15 @@ describe('Create Worker', () => { test('Creates a single Esqueue worker for Reporting', async () => { const exportTypesRegistry = getMockExportTypesRegistry(); - const createWorker = createWorkerFactory(getMockServer(), getMockLogger(), { - exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, - browserDriverFactory: {} as HeadlessChromiumDriverFactory, - }); + const createWorker = createWorkerFactory( + getMockServer(), + {} as ElasticsearchServiceSetup, + getMockLogger(), + { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + } + ); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); @@ -82,10 +88,15 @@ Object { { executeJobFactory: executeJobFactoryStub }, { executeJobFactory: executeJobFactoryStub }, ]); - const createWorker = createWorkerFactory(getMockServer(), getMockLogger(), { - exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, - browserDriverFactory: {} as HeadlessChromiumDriverFactory, - }); + const createWorker = createWorkerFactory( + getMockServer(), + {} as ElasticsearchServiceSetup, + getMockLogger(), + { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + } + ); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 67869016a250be0..2ca638f641291d0 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ElasticsearchServiceSetup } from 'kibana/server'; import { PLUGIN_ID } from '../../common/constants'; import { ExportTypesRegistry, HeadlessChromiumDriverFactory } from '../../types'; import { CancellationToken } from '../../common/cancellation_token'; @@ -29,6 +30,7 @@ interface CreateWorkerFactoryOpts { export function createWorkerFactory( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: Logger, { exportTypesRegistry, browserDriverFactory }: CreateWorkerFactoryOpts ) { @@ -50,7 +52,9 @@ export function createWorkerFactory( ExportTypeDefinition >) { // TODO: the executeJobFn should be unwrapped in the register method of the export types registry - const jobExecutor = exportType.executeJobFactory(server, logger, { browserDriverFactory }); + const jobExecutor = exportType.executeJobFactory(server, elasticsearch, logger, { + browserDriverFactory, + }); jobExecutors.set(exportType.jobType, jobExecutor); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 14c57fa35dcf4ea..1da8a3795aacc5f 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -5,6 +5,7 @@ */ import { get } from 'lodash'; +import { ElasticsearchServiceSetup } from 'kibana/server'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; import { @@ -35,6 +36,7 @@ interface EnqueueJobFactoryOpts { export function enqueueJobFactory( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, parentLogger: Logger, { exportTypesRegistry, esqueue }: EnqueueJobFactoryOpts ): EnqueueJobFn { @@ -61,7 +63,7 @@ export function enqueueJobFactory( } // TODO: the createJobFn should be unwrapped in the register method of the export types registry - const createJob = exportType.createJobFactory(server, logger) as CreateJobFn; + const createJob = exportType.createJobFactory(server, elasticsearch, logger) as CreateJobFn; const payload = await createJob(jobParams, headers, request); const options = { diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js index 31bdf7767983dd6..ebda7ff955b11cd 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/fixtures/legacy_elasticsearch.js @@ -1,10 +1,14 @@ -import { uniqueId, times, random } from 'lodash'; -import * as legacyElasticsearch from 'elasticsearch'; +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ -import { constants } from '../../constants'; +import { uniqueId, times, random } from 'lodash'; +import { errors as esErrors } from 'elasticsearch'; export function ClientMock() { - this.callWithInternalUser = (endpoint, params = {}, ...rest) => { + this.callAsInternalUser = (endpoint, params = {}, ...rest) => { if (endpoint === 'indices.create') { return Promise.resolve({ acknowledged: true }); } @@ -21,12 +25,12 @@ export function ClientMock() { _seq_no: 1, _primary_term: 1, _shards: { total: shardCount, successful: shardCount, failed: 0 }, - created: true + created: true, }); } if (endpoint === 'get') { - if (params === legacyElasticsearch.errors.NotFound) return legacyElasticsearch.errors.NotFound; + if (params === esErrors.NotFound) return esErrors.NotFound; const _source = { jobtype: 'jobtype', @@ -34,7 +38,7 @@ export function ClientMock() { payload: { id: 'sample-job-1', - now: 'Mon Apr 25 2016 14:13:04 GMT-0700 (MST)' + now: 'Mon Apr 25 2016 14:13:04 GMT-0700 (MST)', }, priority: 10, @@ -43,7 +47,7 @@ export function ClientMock() { attempts: 0, max_attempts: 3, status: 'pending', - ...(rest[0] || {}) + ...(rest[0] || {}), }; return Promise.resolve({ @@ -52,7 +56,7 @@ export function ClientMock() { _seq_no: params._seq_no || 1, _primary_term: params._primary_term || 1, found: true, - _source: _source + _source: _source, }); } @@ -68,8 +72,8 @@ export function ClientMock() { _source: { created_at: new Date().toString(), number: random(0, count, true), - ...source - } + ...source, + }, }; }); return Promise.resolve({ @@ -78,13 +82,13 @@ export function ClientMock() { _shards: { total: 5, successful: 5, - failed: 0 + failed: 0, }, hits: { total: count, max_score: null, - hits: hits - } + hits: hits, + }, }); } @@ -96,7 +100,7 @@ export function ClientMock() { _seq_no: params.if_seq_no + 1 || 2, _primary_term: params.if_primary_term + 1 || 2, _shards: { total: shardCount, successful: shardCount, failed: 0 }, - created: true + created: true, }); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js index 23e9aab5bad1158..2944574534a827c 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js @@ -17,7 +17,7 @@ describe('Create Index', function() { beforeEach(function() { client = new ClientMock(); - createSpy = sinon.spy(client, 'callWithInternalUser').withArgs('indices.create'); + createSpy = sinon.spy(client, 'callAsInternalUser').withArgs('indices.create'); }); it('should return true', function() { @@ -75,10 +75,10 @@ describe('Create Index', function() { beforeEach(function() { client = new ClientMock(); sinon - .stub(client, 'callWithInternalUser') + .stub(client, 'callAsInternalUser') .withArgs('indices.exists') .callsFake(() => Promise.resolve(true)); - createSpy = client.callWithInternalUser.withArgs('indices.create'); + createSpy = client.callAsInternalUser.withArgs('indices.create'); }); it('should return true', function() { diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/index.js index 8f1ed69de5e7f60..428c0f0bc0736c4 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/index.js @@ -40,7 +40,7 @@ describe('Esqueue class', function() { describe('Queue construction', function() { it('should ping the ES server', function() { - const pingSpy = sinon.spy(client, 'callWithInternalUser').withArgs('ping'); + const pingSpy = sinon.spy(client, 'callAsInternalUser').withArgs('ping'); new Esqueue('esqueue', { client }); sinon.assert.calledOnce(pingSpy); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js index 2d8410c18ddeab3..c7812ec151b0050 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js @@ -79,7 +79,7 @@ describe('Job Class', function() { beforeEach(function() { type = 'type1'; payload = { id: '123' }; - indexSpy = sinon.spy(client, 'callWithInternalUser').withArgs('index'); + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); }); it('should create the target index', function() { @@ -121,7 +121,7 @@ describe('Job Class', function() { }); it('should refresh the index', function() { - const refreshSpy = client.callWithInternalUser.withArgs('indices.refresh'); + const refreshSpy = client.callAsInternalUser.withArgs('indices.refresh'); const job = new Job(mockQueue, index, type, payload); return job.ready.then(() => { @@ -165,9 +165,9 @@ describe('Job Class', function() { it('should emit error on client index failure', function(done) { const errMsg = 'test document index failure'; - client.callWithInternalUser.restore(); + client.callAsInternalUser.restore(); sinon - .stub(client, 'callWithInternalUser') + .stub(client, 'callAsInternalUser') .withArgs('index') .callsFake(() => Promise.reject(new Error(errMsg))); const job = new Job(mockQueue, index, type, payload); @@ -215,7 +215,7 @@ describe('Job Class', function() { beforeEach(function() { type = 'type1'; payload = { id: '123' }; - indexSpy = sinon.spy(client, 'callWithInternalUser').withArgs('index'); + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); }); it('should set attempt count to 0', function() { @@ -281,7 +281,7 @@ describe('Job Class', function() { authorization: 'Basic cXdlcnR5', }, }; - indexSpy = sinon.spy(client, 'callWithInternalUser').withArgs('index'); + indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index'); }); it('should index the created_by value', function() { @@ -367,10 +367,10 @@ describe('Job Class', function() { }; const job = new Job(mockQueue, index, type, payload, optionals); - return Promise.resolve(client.callWithInternalUser('get', {}, optionals)) + return Promise.resolve(client.callAsInternalUser('get', {}, optionals)) .then(doc => { sinon - .stub(client, 'callWithInternalUser') + .stub(client, 'callAsInternalUser') .withArgs('get') .returns(Promise.resolve(doc)); }) diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js index bd82a9e9f99db94..ad93a1882746dcb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js @@ -288,7 +288,7 @@ describe('Worker class', function() { describe('error handling', function() { it('should pass search errors', function(done) { searchStub = sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .callsFake(() => Promise.reject()); worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); @@ -303,7 +303,7 @@ describe('Worker class', function() { describe('missing index', function() { it('should swallow error', function(done) { searchStub = sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .callsFake(() => Promise.reject({ status: 404 })); worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); @@ -317,7 +317,7 @@ describe('Worker class', function() { it('should return an empty array', function(done) { searchStub = sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .callsFake(() => Promise.reject({ status: 404 })); worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); @@ -343,7 +343,7 @@ describe('Worker class', function() { beforeEach(() => { searchStub = sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .callsFake(() => Promise.resolve({ hits: { hits: [] } })); anchorMoment = moment(anchor); @@ -417,10 +417,10 @@ describe('Worker class', function() { type: 'test', id: 12345, }; - return mockQueue.client.callWithInternalUser('get', params).then(jobDoc => { + return mockQueue.client.callAsInternalUser('get', params).then(jobDoc => { job = jobDoc; worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); - updateSpy = sinon.spy(mockQueue.client, 'callWithInternalUser').withArgs('update'); + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); }); }); @@ -483,9 +483,9 @@ describe('Worker class', function() { }); it('should reject the promise on conflict errors', function() { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .returns(Promise.reject({ statusCode: 409 })); return worker._claimJob(job).catch(err => { @@ -494,9 +494,9 @@ describe('Worker class', function() { }); it('should reject the promise on other errors', function() { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .returns(Promise.reject({ statusCode: 401 })); return worker._claimJob(job).catch(err => { @@ -532,12 +532,12 @@ describe('Worker class', function() { }); afterEach(() => { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); }); it('should emit for errors from claiming job', function(done) { sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 401 }); @@ -558,7 +558,7 @@ describe('Worker class', function() { it('should reject the promise if an error claiming the job', function() { sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 409 }); return worker._claimPendingJobs(getMockJobs()).catch(err => { @@ -568,7 +568,7 @@ describe('Worker class', function() { it('should get the pending job', function() { sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .resolves({ test: 'cool' }); sinon.stub(worker, '_performJob').callsFake(identity); @@ -590,10 +590,10 @@ describe('Worker class', function() { anchorMoment = moment(anchor); clock = sinon.useFakeTimers(anchorMoment.valueOf()); - return mockQueue.client.callWithInternalUser('get').then(jobDoc => { + return mockQueue.client.callAsInternalUser('get').then(jobDoc => { job = jobDoc; worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); - updateSpy = sinon.spy(mockQueue.client, 'callWithInternalUser').withArgs('update'); + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); }); }); @@ -625,18 +625,18 @@ describe('Worker class', function() { }); it('should return true on conflict errors', function() { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 409 }); return worker._failJob(job).then(res => expect(res).to.equal(true)); }); it('should return false on other document update errors', function() { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 401 }); return worker._failJob(job).then(res => expect(res).to.equal(false)); @@ -672,9 +672,9 @@ describe('Worker class', function() { }); it('should emit on other document update errors', function(done) { - mockQueue.client.callWithInternalUser.restore(); + mockQueue.client.callAsInternalUser.restore(); sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 401 }); @@ -703,9 +703,9 @@ describe('Worker class', function() { value: random(0, 100, true), }; - return mockQueue.client.callWithInternalUser('get', {}, { payload }).then(jobDoc => { + return mockQueue.client.callAsInternalUser('get', {}, { payload }).then(jobDoc => { job = jobDoc; - updateSpy = sinon.spy(mockQueue.client, 'callWithInternalUser').withArgs('update'); + updateSpy = sinon.spy(mockQueue.client, 'callAsInternalUser').withArgs('update'); }); }); @@ -871,7 +871,7 @@ describe('Worker class', function() { }; sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('update') .rejects({ statusCode: 413 }); @@ -893,7 +893,7 @@ describe('Worker class', function() { describe('search failure', function() { it('causes _processPendingJobs to reject the Promise', function() { sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .rejects(new Error('test error')); worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions); @@ -996,7 +996,7 @@ describe('Worker class', function() { beforeEach(function() { sinon - .stub(mockQueue.client, 'callWithInternalUser') + .stub(mockQueue.client, 'callAsInternalUser') .withArgs('search') .callsFake(() => Promise.resolve({ hits: { hits: [] } })); }); @@ -1086,20 +1086,12 @@ describe('Format Job Object', () => { }); }); -// FAILING: https://github.com/elastic/kibana/issues/51372 -describe.skip('Get Doc Path from ES Response', () => { +describe('Get Doc Path from ES Response', () => { it('returns a formatted string after response of an update', function() { const responseMock = { _index: 'foo', _id: 'booId', }; - expect(getUpdatedDocPath(responseMock)).equal('/foo/_doc/booId'); - }); - it('returns the same formatted string even if there is no _doc provided', function() { - const responseMock = { - _index: 'foo', - _id: 'booId', - }; - expect(getUpdatedDocPath(responseMock)).equal('/foo/_doc/booId'); + expect(getUpdatedDocPath(responseMock)).equal('/foo/booId'); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js index 670c2907fb83226..465f27a817ba757 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js @@ -78,13 +78,13 @@ export function createIndex(client, indexName, indexSettings = {}) { }; return client - .callWithInternalUser('indices.exists', { + .callAsInternalUser('indices.exists', { index: indexName, }) .then(exists => { if (!exists) { return client - .callWithInternalUser('indices.create', { + .callAsInternalUser('indices.create', { index: indexName, body: body, }) diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/index.js index b42ef84168940db..bd30ca9ae0f29e1 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/index.js @@ -32,7 +32,7 @@ export class Esqueue extends EventEmitter { } _initTasks() { - const initTasks = [this.client.callWithInternalUser('ping')]; + const initTasks = [this.client.callAsInternalUser('ping')]; return Promise.all(initTasks).catch(err => { this._logger(['initTasks', 'error'], err); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js index a7d8f4df3fd5411..826fcf360a4ca28 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js @@ -78,7 +78,7 @@ export class Job extends events.EventEmitter { } this.ready = createIndex(this._client, this.index, this.indexSettings) - .then(() => this._client.callWithInternalUser('index', indexParams)) + .then(() => this._client.callAsInternalUser('index', indexParams)) .then(doc => { this.document = { id: doc._id, @@ -89,7 +89,7 @@ export class Job extends events.EventEmitter { this.debug(`Job created in index ${this.index}`); return this._client - .callWithInternalUser('indices.refresh', { + .callAsInternalUser('indices.refresh', { index: this.index, }) .then(() => { @@ -111,7 +111,7 @@ export class Job extends events.EventEmitter { get() { return this.ready .then(() => { - return this._client.callWithInternalUser('get', { + return this._client.callAsInternalUser('get', { index: this.index, id: this.id, }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js index 7ad84460a0c4596..43735979422783d 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js @@ -160,7 +160,7 @@ export class Worker extends events.EventEmitter { }; return this._client - .callWithInternalUser('update', { + .callAsInternalUser('update', { index: job._index, id: job._id, if_seq_no: job._seq_no, @@ -199,7 +199,7 @@ export class Worker extends events.EventEmitter { }); return this._client - .callWithInternalUser('update', { + .callAsInternalUser('update', { index: job._index, id: job._id, if_seq_no: job._seq_no, @@ -286,7 +286,7 @@ export class Worker extends events.EventEmitter { }; return this._client - .callWithInternalUser('update', { + .callAsInternalUser('update', { index: job._index, id: job._id, if_seq_no: job._seq_no, @@ -431,7 +431,7 @@ export class Worker extends events.EventEmitter { }; return this._client - .callWithInternalUser('search', { + .callAsInternalUser('search', { index: `${this.queue.index}-*`, body: query, }) diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts index 0c16f780c34acda..3562834230ea1df 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { errors as elasticsearchErrors } from 'elasticsearch'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { get } from 'lodash'; -import { ServerFacade, JobSource } from '../../types'; +import { JobSource, ServerFacade } from '../../types'; +const esErrors = elasticsearchErrors as Record; const defaultSize = 10; interface QueryBody { @@ -34,12 +37,9 @@ interface CountAggResult { count: number; } -export function jobsQueryFactory(server: ServerFacade) { +export function jobsQueryFactory(server: ServerFacade, elasticsearch: ElasticsearchServiceSetup) { const index = server.config().get('xpack.reporting.index'); - // @ts-ignore `errors` does not exist on type Cluster - const { callWithInternalUser, errors: esErrors } = server.plugins.elasticsearch.getCluster( - 'admin' - ); + const { callAsInternalUser } = elasticsearch.adminClient; function getUsername(user: any) { return get(user, 'username', false); @@ -61,7 +61,7 @@ export function jobsQueryFactory(server: ServerFacade) { body: Object.assign(defaultBody[queryType] || {}, body), }; - return callWithInternalUser(queryType, query).catch(err => { + return callAsInternalUser(queryType, query).catch(err => { if (err instanceof esErrors['401']) return; if (err instanceof esErrors['403']) return; if (err instanceof esErrors['404']) return; diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts index 79a64bd82d0228f..028d8fa143487c5 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts @@ -5,7 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import { ServerFacade, Logger } from '../../../types'; +import { ElasticsearchServiceSetup } from 'kibana/server'; +import { Logger, ServerFacade } from '../../../types'; import { HeadlessChromiumDriverFactory } from '../../browsers/chromium/driver_factory'; import { validateBrowser } from './validate_browser'; import { validateEncryptionKey } from './validate_encryption_key'; @@ -14,6 +15,7 @@ import { validateServerHost } from './validate_server_host'; export async function runValidations( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: Logger, browserFactory: HeadlessChromiumDriverFactory ) { @@ -21,7 +23,7 @@ export async function runValidations( await Promise.all([ validateBrowser(server, browserFactory, logger), validateEncryptionKey(server, logger), - validateMaxContentLength(server, logger), + validateMaxContentLength(server, elasticsearch, logger), validateServerHost(server), ]); logger.debug( diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_max_content_length.js b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js similarity index 65% rename from x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_max_content_length.js rename to x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js index 48a58618f34cc75..942dcaf842696c3 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_max_content_length.js +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.test.js @@ -3,14 +3,26 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; + import sinon from 'sinon'; -import { validateMaxContentLength } from '../validate_max_content_length'; +import { validateMaxContentLength } from './validate_max_content_length'; const FIVE_HUNDRED_MEGABYTES = 524288000; const ONE_HUNDRED_MEGABYTES = 104857600; describe('Reporting: Validate Max Content Length', () => { + const elasticsearch = { + dataClient: { + callAsInternalUser: () => ({ + defaults: { + http: { + max_content_length: '100mb', + }, + }, + }), + }, + }; + const logger = { warning: sinon.spy(), }; @@ -24,22 +36,20 @@ describe('Reporting: Validate Max Content Length', () => { config: () => ({ get: sinon.stub().returns(FIVE_HUNDRED_MEGABYTES), }), - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithInternalUser: () => ({ - defaults: { - http: { - max_content_length: '100mb', - }, - }, - }), - }), - }, + }; + const elasticsearch = { + dataClient: { + callAsInternalUser: () => ({ + defaults: { + http: { + max_content_length: '100mb', + }, + }, + }), }, }; - await validateMaxContentLength(server, logger); + await validateMaxContentLength(server, elasticsearch, logger); sinon.assert.calledWithMatch( logger.warning, @@ -64,22 +74,11 @@ describe('Reporting: Validate Max Content Length', () => { config: () => ({ get: sinon.stub().returns(ONE_HUNDRED_MEGABYTES), }), - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithInternalUser: () => ({ - defaults: { - http: { - max_content_length: '100mb', - }, - }, - }), - }), - }, - }, }; - expect(async () => validateMaxContentLength(server, logger.warning)).not.to.throwError(); + expect( + async () => await validateMaxContentLength(server, elasticsearch, logger.warning) + ).not.toThrow(); sinon.assert.notCalled(logger.warning); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts index f91cd40bfd3c733..ce4a5b93e74310f 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts @@ -3,18 +3,24 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import numeral from '@elastic/numeral'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { defaults, get } from 'lodash'; import { Logger, ServerFacade } from '../../../types'; const KIBANA_MAX_SIZE_BYTES_PATH = 'xpack.reporting.csv.maxSizeBytes'; const ES_MAX_SIZE_BYTES_PATH = 'http.max_content_length'; -export async function validateMaxContentLength(server: ServerFacade, logger: Logger) { +export async function validateMaxContentLength( + server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, + logger: Logger +) { const config = server.config(); - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('data'); + const { callAsInternalUser } = elasticsearch.dataClient; - const elasticClusterSettingsResponse = await callWithInternalUser('cluster.getSettings', { + const elasticClusterSettingsResponse = await callAsInternalUser('cluster.getSettings', { includeDefaults: true, }); const { persistent, transient, defaults: defaultSettings } = elasticClusterSettingsResponse; diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index cf66ec74969cae6..a2938d442f7df05 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -5,20 +5,26 @@ */ import { Legacy } from 'kibana'; -import { CoreSetup, CoreStart, Plugin, LoggerFactory } from 'src/core/server'; +import { + CoreSetup, + CoreStart, + ElasticsearchServiceSetup, + LoggerFactory, + Plugin, +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { PluginSetupContract as SecurityPluginSetup } from '../../../../plugins/security/server'; -import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; // @ts-ignore import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; +import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import { PLUGIN_ID } from '../common/constants'; +import { logConfiguration } from '../log_configuration'; import { ReportingPluginSpecOptions } from '../types.d'; -import { registerRoutes } from './routes'; -import { checkLicenseFactory, getExportTypesRegistry, runValidations, LevelLogger } from './lib'; import { createBrowserDriverFactory } from './browsers'; +import { checkLicenseFactory, getExportTypesRegistry, LevelLogger, runValidations } from './lib'; +import { registerRoutes } from './routes'; import { registerReportingUsageCollector } from './usage'; -import { logConfiguration } from '../log_configuration'; -import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; export interface ReportingInitializerContext { logger: LoggerFactory; @@ -29,22 +35,16 @@ export type ReportingSetup = object; export type ReportingStart = object; export interface ReportingSetupDeps { + elasticsearch: ElasticsearchServiceSetup; usageCollection: UsageCollectionSetup; security: SecurityPluginSetup; } export type ReportingStartDeps = object; -type LegacyPlugins = Legacy.Server['plugins']; - export interface LegacySetup { config: Legacy.Server['config']; info: Legacy.Server['info']; - plugins: { - elasticsearch: LegacyPlugins['elasticsearch']; - xpack_main: XPackMainPlugin & { - status?: any; - }; - }; + plugins: { xpack_main: XPackMainPlugin & { status?: any } }; route: Legacy.Server['route']; savedObjects: Legacy.Server['savedObjects']; uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; @@ -76,10 +76,10 @@ export function reportingPluginFactory( public async setup(core: CoreSetup, plugins: ReportingSetupDeps): Promise { const exportTypesRegistry = getExportTypesRegistry(); + const { usageCollection, elasticsearch } = plugins; let isCollectorReady = false; // Register a function with server to manage the collection of usage stats - const { usageCollection } = plugins; registerReportingUsageCollector( usageCollection, __LEGACY, @@ -91,7 +91,7 @@ export function reportingPluginFactory( const browserDriverFactory = await createBrowserDriverFactory(__LEGACY, logger); logConfiguration(__LEGACY, logger); - runValidations(__LEGACY, logger, browserDriverFactory); + runValidations(__LEGACY, elasticsearch, logger, browserDriverFactory); const { xpack_main: xpackMainPlugin } = __LEGACY.plugins; mirrorPluginStatus(xpackMainPlugin, legacyPlugin); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index f3ed760bba43021..fd1d85fef0f21bc 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -36,6 +36,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( parentLogger: Logger ) { const routeOptions = getRouteOptionsCsv(server, plugins, parentLogger); + const { elasticsearch } = plugins; /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: @@ -57,8 +58,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( * * Calling an execute job factory requires passing a browserDriverFactory option, so we should not call the factory from here */ - const createJobFn = createJobFactory(server, logger); - const executeJobFn = executeJobFactory(server, logger, { + const createJobFn = createJobFactory(server, elasticsearch, logger); + const executeJobFn = executeJobFactory(server, elasticsearch, logger, { browserDriverFactory: {} as HeadlessChromiumDriverFactory, }); const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 3c9ef6987b2d956..02a9541484bc635 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -5,6 +5,7 @@ */ import boom from 'boom'; +import { errors as elasticsearchErrors } from 'elasticsearch'; import { Legacy } from 'kibana'; import { API_BASE_URL } from '../../common/constants'; import { @@ -21,6 +22,8 @@ import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject' import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { makeRequestFacade } from './lib/make_request_facade'; +const esErrors = elasticsearchErrors as Record; + export function registerJobGenerationRoutes( server: ServerFacade, plugins: ReportingSetupDeps, @@ -30,11 +33,15 @@ export function registerJobGenerationRoutes( ) { const config = server.config(); const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; - // @ts-ignore TODO - const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); - - const esqueue = createQueueFactory(server, logger, { exportTypesRegistry, browserDriverFactory }); - const enqueueJob = enqueueJobFactory(server, logger, { exportTypesRegistry, esqueue }); + const { elasticsearch } = plugins; + const esqueue = createQueueFactory(server, elasticsearch, logger, { + exportTypesRegistry, + browserDriverFactory, + }); + const enqueueJob = enqueueJobFactory(server, elasticsearch, logger, { + exportTypesRegistry, + esqueue, + }); /* * Generates enqueued job details to use in responses diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js index c9d4f9fc027be3a..811c81c502b8124 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js @@ -43,18 +43,12 @@ beforeEach(() => { jobContentEncoding: 'base64', jobContentExtension: 'pdf', }); - mockServer.plugins = { - elasticsearch: { - getCluster: memoize(() => ({ callWithInternalUser: jest.fn() })), - createCluster: () => ({ - callWithRequest: jest.fn(), - callWithInternalUser: jest.fn(), - }), - }, - }; }); const mockPlugins = { + elasticsearch: { + adminClient: { callAsInternalUser: jest.fn() }, + }, security: null, }; @@ -67,9 +61,9 @@ const getHits = (...sources) => { }; test(`returns 404 if job not found`, async () => { - mockServer.plugins.elasticsearch - .getCluster('admin') - .callWithInternalUser.mockReturnValue(Promise.resolve(getHits())); + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), + }; registerJobInfoRoutes(mockServer, mockPlugins, exportTypesRegistry, mockLogger); @@ -84,9 +78,11 @@ test(`returns 404 if job not found`, async () => { }); test(`returns 401 if not valid job type`, async () => { - mockServer.plugins.elasticsearch - .getCluster('admin') - .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))); + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest + .fn() + .mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))), + }; registerJobInfoRoutes(mockServer, mockPlugins, exportTypesRegistry, mockLogger); @@ -101,11 +97,13 @@ test(`returns 401 if not valid job type`, async () => { describe(`when job is incomplete`, () => { const getIncompleteResponse = async () => { - mockServer.plugins.elasticsearch - .getCluster('admin') - .callWithInternalUser.mockReturnValue( - Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) - ); + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest + .fn() + .mockReturnValue( + Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' })) + ), + }; registerJobInfoRoutes(mockServer, mockPlugins, exportTypesRegistry, mockLogger); @@ -145,9 +143,9 @@ describe(`when job is failed`, () => { status: 'failed', output: { content: 'job failure message' }, }); - mockServer.plugins.elasticsearch - .getCluster('admin') - .callWithInternalUser.mockReturnValue(Promise.resolve(hits)); + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; registerJobInfoRoutes(mockServer, mockPlugins, exportTypesRegistry, mockLogger); @@ -190,9 +188,9 @@ describe(`when job is completed`, () => { title, }, }); - mockServer.plugins.elasticsearch - .getCluster('admin') - .callWithInternalUser.mockReturnValue(Promise.resolve(hits)); + mockPlugins.elasticsearch.adminClient = { + callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), + }; registerJobInfoRoutes(mockServer, mockPlugins, exportTypesRegistry, mockLogger); diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index f9b731db5a702b6..daabc2cf22f4e27 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -38,7 +38,8 @@ export function registerJobInfoRoutes( exportTypesRegistry: ExportTypesRegistry, logger: Logger ) { - const jobsQuery = jobsQueryFactory(server); + const { elasticsearch } = plugins; + const jobsQuery = jobsQueryFactory(server, elasticsearch); const getRouteConfig = getRouteConfigFactoryManagementPre(server, plugins, logger); const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server, plugins, logger); @@ -137,7 +138,7 @@ export function registerJobInfoRoutes( }); // trigger a download of the output from a job - const jobResponseHandler = jobResponseHandlerFactory(server, exportTypesRegistry); + const jobResponseHandler = jobResponseHandlerFactory(server, elasticsearch, exportTypesRegistry); server.route({ path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts index 3ba7aa30eedcb74..62f0d0a72b389af 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -5,10 +5,11 @@ */ import Boom from 'boom'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { ResponseToolkit } from 'hapi'; -import { ServerFacade, ExportTypesRegistry } from '../../../types'; -import { jobsQueryFactory } from '../../lib/jobs_query'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; +import { ExportTypesRegistry, ServerFacade } from '../../../types'; +import { jobsQueryFactory } from '../../lib/jobs_query'; import { getDocumentPayloadFactory } from './get_document_payload'; interface JobResponseHandlerParams { @@ -21,9 +22,10 @@ interface JobResponseHandlerOpts { export function jobResponseHandlerFactory( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, exportTypesRegistry: ExportTypesRegistry ) { - const jobsQuery = jobsQueryFactory(server); + const jobsQuery = jobsQueryFactory(server, elasticsearch); const getDocumentPayload = getDocumentPayloadFactory(server, exportTypesRegistry); return function jobResponseHandler( diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 9ba016d8b828d84..a4ff39b23747dd1 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ResponseObject } from 'hapi'; import { EventEmitter } from 'events'; +import { ResponseObject } from 'hapi'; +import { ElasticsearchServiceSetup } from 'kibana/server'; import { Legacy } from 'kibana'; import { CallCluster } from '../../../../src/legacy/core_plugins/elasticsearch'; import { CancellationToken } from './common/cancellation_token'; -import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; -import { LegacySetup } from './server/plugin'; +import { LevelLogger } from './server/lib/level_logger'; +import { LegacySetup, ReportingSetupDeps } from './server/plugin'; export type ReportingPlugin = object; // For Plugin contract @@ -276,10 +277,12 @@ export interface ESQueueInstance { export type CreateJobFactory = ( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: LevelLogger ) => CreateJobFnType; export type ExecuteJobFactory = ( server: ServerFacade, + elasticsearch: ElasticsearchServiceSetup, logger: LevelLogger, opts: { browserDriverFactory: HeadlessChromiumDriverFactory; @@ -302,10 +305,10 @@ export interface ExportTypeDefinition< validLicenses: string[]; } -export { ExportTypesRegistry } from './server/lib/export_types_registry'; +export { CancellationToken } from './common/cancellation_token'; export { HeadlessChromiumDriver } from './server/browsers/chromium/driver'; export { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; -export { CancellationToken } from './common/cancellation_token'; +export { ExportTypesRegistry } from './server/lib/export_types_registry'; // Prefer to import this type using: `import { LevelLogger } from 'relative/path/server/lib';` export { LevelLogger as Logger }; diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index cd9b7f59226b0b8..0a3e447ac64a1cc 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { get } from 'lodash/fp'; import { resolve } from 'path'; import { Server } from 'hapi'; import { Root } from 'joi'; @@ -155,6 +156,9 @@ export const siem = (kibana: any) => { const initializerContext = { ...coreContext, env } as PluginInitializerContext; const serverFacade = { config, + usingEphemeralEncryptionKey: + get('usingEphemeralEncryptionKey', newPlatform.setup.plugins.encryptedSavedObjects) ?? + false, plugins: { alerting: plugins.alerting, actions: newPlatform.start.plugins.actions, diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 14d40f9ffbc37af..d77d6283692a243 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -29,6 +29,7 @@ interface UsePrePackagedRuleProps { hasIndexWrite: boolean | null; hasManageApiKey: boolean | null; isAuthenticated: boolean | null; + hasEncryptionKey: boolean | null; isSignalIndexExists: boolean | null; } @@ -38,6 +39,7 @@ interface UsePrePackagedRuleProps { * @param hasIndexWrite boolean * @param hasManageApiKey boolean * @param isAuthenticated boolean + * @param hasEncryptionKey boolean * @param isSignalIndexExists boolean * */ @@ -46,6 +48,7 @@ export const usePrePackagedRules = ({ hasIndexWrite, hasManageApiKey, isAuthenticated, + hasEncryptionKey, isSignalIndexExists, }: UsePrePackagedRuleProps): Return => { const [rulesStatus, setRuleStatus] = useState< @@ -117,6 +120,7 @@ export const usePrePackagedRules = ({ hasIndexWrite && hasManageApiKey && isAuthenticated && + hasEncryptionKey && isSignalIndexExists ) { setLoadingCreatePrePackagedRules(true); @@ -180,7 +184,14 @@ export const usePrePackagedRules = ({ isSubscribed = false; abortCtrl.abort(); }; - }, [canUserCRUD, hasIndexWrite, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + }, [ + canUserCRUD, + hasIndexWrite, + hasManageApiKey, + isAuthenticated, + hasEncryptionKey, + isSignalIndexExists, + ]); return { loading, diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts index ea4860dafd40f20..752de13567e5c86 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -97,4 +97,5 @@ export interface Privilege { }; }; is_authenticated: boolean; + has_encryption_key: boolean; } diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index b93009c8ce2c281..55f3386b503d882 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -14,6 +14,7 @@ import * as i18n from './translations'; interface Return { loading: boolean; isAuthenticated: boolean | null; + hasEncryptionKey: boolean | null; hasIndexManage: boolean | null; hasManageApiKey: boolean | null; hasIndexWrite: boolean | null; @@ -25,9 +26,17 @@ interface Return { export const usePrivilegeUser = (): Return => { const [loading, setLoading] = useState(true); const [privilegeUser, setPrivilegeUser] = useState< - Pick + Pick< + Return, + | 'isAuthenticated' + | 'hasEncryptionKey' + | 'hasIndexManage' + | 'hasManageApiKey' + | 'hasIndexWrite' + > >({ isAuthenticated: null, + hasEncryptionKey: null, hasIndexManage: null, hasManageApiKey: null, hasIndexWrite: null, @@ -50,6 +59,7 @@ export const usePrivilegeUser = (): Return => { const indexName = Object.keys(privilege.index)[0]; setPrivilegeUser({ isAuthenticated: privilege.is_authenticated, + hasEncryptionKey: privilege.has_encryption_key, hasIndexManage: privilege.index[indexName].manage, hasIndexWrite: privilege.index[indexName].create || @@ -67,6 +77,7 @@ export const usePrivilegeUser = (): Return => { if (isSubscribed) { setPrivilegeUser({ isAuthenticated: false, + hasEncryptionKey: false, hasIndexManage: false, hasManageApiKey: false, hasIndexWrite: false, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.tsx new file mode 100644 index 000000000000000..2d517717ac59d57 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback, useState } from 'react'; + +import * as i18n from './translations'; + +const NoApiIntegrationKeyCallOutComponent = () => { + const [showCallOut, setShowCallOut] = useState(true); + const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); + + return showCallOut ? ( + +

{i18n.NO_API_INTEGRATION_KEY_CALLOUT_MSG}

+ + {i18n.DISMISS_CALLOUT} + +
+ ) : null; +}; + +export const NoApiIntegrationKeyCallOut = memo(NoApiIntegrationKeyCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/translations.ts new file mode 100644 index 000000000000000..84804af8840f915 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/translations.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_API_INTEGRATION_KEY_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle', + { + defaultMessage: 'API integration key required', + } +); + +export const NO_API_INTEGRATION_KEY_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.noApiIntegrationKeyCallOutMsg', + { + defaultMessage: `A new encryption key is generated for saved objects each time you start Kibana. Without a persistent key, you cannot delete or modify rules after Kibana restarts. To set a persistent key, add the xpack.encryptedSavedObjects.encryptionKey setting with any text value of 32 or more characters to the kibana.yml file.`, + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx index a33efeda2196b52..003d2baa53dbc19 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -5,7 +5,7 @@ */ import dateMath from '@elastic/datemath'; -import { getOr } from 'lodash/fp'; +import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; import { updateSignalStatus } from '../../../../containers/detection_engine/signals/api'; @@ -78,6 +78,7 @@ export const sendSignalToTimelineAction = async ({ ecsData, updateTimelineIsLoading, }: SendSignalToTimelineActionProps) => { + let openSignalInBasicTimeline = true; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; @@ -105,52 +106,57 @@ export const sendSignalToTimelineAction = async ({ id: timelineId, }, }); - const timelineTemplate: TimelineResult = omitTypenameInTimeline( getOr({}, 'data.getOneTimeline', responseTimeline) ); - const { timeline } = formatTimelineResultToModel(timelineTemplate, true); - const query = replaceTemplateFieldFromQuery( - timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', - ecsData - ); - const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], ecsData); - const dataProviders = replaceTemplateFieldFromDataProviders( - timeline.dataProviders ?? [], - ecsData - ); - createTimeline({ - from, - timeline: { - ...timeline, - dataProviders, - eventType: 'all', - filters, - dateRange: { - start: from, - end: to, - }, - kqlQuery: { - filterQuery: { - kuery: { + if (!isEmpty(timelineTemplate)) { + openSignalInBasicTimeline = false; + const { timeline } = formatTimelineResultToModel(timelineTemplate, true); + const query = replaceTemplateFieldFromQuery( + timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', + ecsData + ); + const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], ecsData); + const dataProviders = replaceTemplateFieldFromDataProviders( + timeline.dataProviders ?? [], + ecsData + ); + createTimeline({ + from, + timeline: { + ...timeline, + dataProviders, + eventType: 'all', + filters, + dateRange: { + start: from, + end: to, + }, + kqlQuery: { + filterQuery: { + kuery: { + kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', + expression: query, + }, + serializedQuery: convertKueryToElasticSearchQuery(query), + }, + filterQueryDraft: { kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', expression: query, }, - serializedQuery: convertKueryToElasticSearchQuery(query), - }, - filterQueryDraft: { - kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', - expression: query, }, + show: true, }, - show: true, - }, - to, - }); + to, + }); + } } catch { + openSignalInBasicTimeline = true; updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); } - } else { + } + + if (openSignalInBasicTimeline) { createTimeline({ from, timeline: { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 0f6a51e52cd2e86..a96913f2ad541f4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -18,6 +18,7 @@ export interface State { hasManageApiKey: boolean | null; isSignalIndexExists: boolean | null; isAuthenticated: boolean | null; + hasEncryptionKey: boolean | null; loading: boolean; signalIndexName: string | null; } @@ -29,6 +30,7 @@ const initialState: State = { hasManageApiKey: null, isSignalIndexExists: null, isAuthenticated: null, + hasEncryptionKey: null, loading: true, signalIndexName: null, }; @@ -55,6 +57,10 @@ export type Action = type: 'updateIsAuthenticated'; isAuthenticated: boolean | null; } + | { + type: 'updateHasEncryptionKey'; + hasEncryptionKey: boolean | null; + } | { type: 'updateCanUserCRUD'; canUserCRUD: boolean | null; @@ -102,6 +108,12 @@ export const userInfoReducer = (state: State, action: Action): State => { isAuthenticated: action.isAuthenticated, }; } + case 'updateHasEncryptionKey': { + return { + ...state, + hasEncryptionKey: action.hasEncryptionKey, + }; + } case 'updateCanUserCRUD': { return { ...state, @@ -142,6 +154,7 @@ export const useUserInfo = (): State => { hasManageApiKey, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, loading, signalIndexName, }, @@ -150,6 +163,7 @@ export const useUserInfo = (): State => { const { loading: privilegeLoading, isAuthenticated: isApiAuthenticated, + hasEncryptionKey: isApiEncryptionKey, hasIndexManage: hasApiIndexManage, hasIndexWrite: hasApiIndexWrite, hasManageApiKey: hasApiManageApiKey, @@ -205,6 +219,12 @@ export const useUserInfo = (): State => { } }, [loading, isAuthenticated, isApiAuthenticated]); + useEffect(() => { + if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) { + dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey }); + } + }, [loading, hasEncryptionKey, isApiEncryptionKey]); + useEffect(() => { if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) { dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD }); @@ -220,6 +240,7 @@ export const useUserInfo = (): State => { useEffect(() => { if ( isAuthenticated && + hasEncryptionKey && hasIndexManage && isSignalIndexExists != null && !isSignalIndexExists && @@ -227,12 +248,13 @@ export const useUserInfo = (): State => { ) { createSignalIndex(); } - }, [createSignalIndex, isAuthenticated, isSignalIndexExists, hasIndexManage]); + }, [createSignalIndex, isAuthenticated, hasEncryptionKey, isSignalIndexExists, hasIndexManage]); return { loading, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, canUserCRUD, hasIndexManage, hasIndexWrite, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index b6ddb4de9fd3904..d854c377e6ec869 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -18,7 +18,10 @@ import { GlobalTime } from '../../containers/global_time'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { AlertsTable } from '../../components/alerts_viewer/alerts_table'; import { FiltersGlobal } from '../../components/filters_global'; -import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine'; +import { + getDetectionEngineTabUrl, + getRulesUrl, +} from '../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; import { State } from '../../store'; @@ -30,6 +33,7 @@ import { InputsRange } from '../../store/inputs/model'; import { AlertsByCategory } from '../overview/alerts_by_category'; import { useSignalInfo } from './components/signals_info'; import { SignalsTable } from './components/signals'; +import { NoApiIntegrationKeyCallOut } from './components/no_api_integration_callout'; import { NoWriteSignalsCallOut } from './components/no_write_signals_callout'; import { SignalsHistogramPanel } from './components/signals_histogram_panel'; import { signalsHistogramOptions } from './components/signals_histogram_panel/config'; @@ -79,6 +83,7 @@ const DetectionEnginePageComponent: React.FC loading, isSignalIndexExists, isAuthenticated: isUserAuthenticated, + hasEncryptionKey, canUserCRUD, signalIndexName, hasIndexWrite, @@ -101,7 +106,7 @@ const DetectionEnginePageComponent: React.FC isSelected={tab.id === tabName} disabled={tab.disabled} key={tab.id} - href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`} + href={getDetectionEngineTabUrl(tab.id)} > {tab.name} @@ -134,6 +139,7 @@ const DetectionEnginePageComponent: React.FC return ( <> + {hasEncryptionKey != null && !hasEncryptionKey && } {hasIndexWrite != null && !hasIndexWrite && } {({ indicesExist, indexPattern }) => { @@ -155,7 +161,7 @@ const DetectionEnginePageComponent: React.FC } title={i18n.PAGE_TITLE} > - + {i18n.BUTTON_MANAGE_RULES} @@ -184,7 +190,7 @@ const DetectionEnginePageComponent: React.FC Cancel diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx index 91b2ee283609fa8..9a68797aea79b2d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx @@ -90,6 +90,11 @@ export const ImportRuleModalComponent = ({ } }, [selectedFiles, overwrite]); + const handleCloseModal = useCallback(() => { + setSelectedFiles(null); + closeModal(); + }, [closeModal]); + return ( <> {showModal && ( @@ -125,7 +130,7 @@ export const ImportRuleModalComponent = ({ - {i18n.CANCEL_BUTTON} + {i18n.CANCEL_BUTTON} = ({ min: 0, fullWidth: false, disabled: isLoading, - options: severityOptions, showTicks: true, tickInterval: 25, }, @@ -239,8 +238,13 @@ const StepAboutRuleComponent: FC = ({ {({ severity }) => { const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; + const severityField = form.getFields().severity; const riskScoreField = form.getFields().riskScore; - if (newRiskScore != null && riskScoreField.value !== newRiskScore) { + if ( + severityField.value !== severity && + newRiskScore != null && + riskScoreField.value !== newRiskScore + ) { riskScoreField.setValue(newRiskScore); } return null; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 55b838077988cce..3adc22329ac4f64 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -23,6 +23,7 @@ import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import * as RuleI18n from '../translations'; +import { redirectToDetections } from '../helpers'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; import * as i18n from './translations'; @@ -69,6 +70,7 @@ const CreateRulePageComponent: React.FC = () => { loading, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, canUserCRUD, hasManageApiKey, } = useUserInfo(); @@ -239,11 +241,7 @@ const CreateRulePageComponent: React.FC = () => { return ; } - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { return ; } else if (userHasNoPermissions) { return ; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 7b615d5f159c2db..bac1494c4fdd81c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -25,9 +25,9 @@ import { connect } from 'react-redux'; import { FiltersGlobal } from '../../../../components/filters_global'; import { FormattedDate } from '../../../../components/formatted_date'; import { - getDetectionEngineUrl, getEditRuleUrl, getRulesUrl, + DETECTION_ENGINE_PAGE_NAME, } from '../../../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../../components/search_bar'; import { WrapperPage } from '../../../../components/wrapper_page'; @@ -54,7 +54,7 @@ import * as detectionI18n from '../../translations'; import { ReadOnlyCallOut } from '../components/read_only_callout'; import { RuleSwitch } from '../components/rule_switch'; import { StepPanel } from '../components/step_panel'; -import { getStepsData } from '../helpers'; +import { getStepsData, redirectToDetections } from '../helpers'; import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../containers/global_time'; @@ -113,6 +113,7 @@ const RuleDetailsPageComponent: FC = ({ loading, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, canUserCRUD, hasManageApiKey, hasIndexWrite, @@ -236,12 +237,8 @@ const RuleDetailsPageComponent: FC = ({ [ruleEnabled, setRuleEnabled] ); - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; } return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 65f4bd2edf7cd02..99fcff6b8d2fda4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -32,7 +32,7 @@ import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; import { formatRule } from '../create/helpers'; -import { getStepsData } from '../helpers'; +import { getStepsData, redirectToDetections } from '../helpers'; import * as ruleI18n from '../translations'; import { RuleStep, DefineStepRule, AboutStepRule, ScheduleStepRule } from '../types'; import * as i18n from './translations'; @@ -56,6 +56,7 @@ const EditRulePageComponent: FC = () => { loading: initLoading, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, canUserCRUD, hasManageApiKey, } = useUserInfo(); @@ -270,11 +271,7 @@ const EditRulePageComponent: FC = () => { return ; } - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { return ; } else if (userHasNoPermissions) { return ; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index ce0d50d9b610654..4e98fc17404c924 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -138,3 +138,13 @@ export const setFieldValue = ( form.setFieldValue(key, val); } }); + +export const redirectToDetections = ( + isSignalIndexExists: boolean | null, + isAuthenticated: boolean | null, + hasEncryptionKey: boolean | null +) => + isSignalIndexExists != null && + isAuthenticated != null && + hasEncryptionKey != null && + (!isSignalIndexExists || !isAuthenticated || !hasEncryptionKey); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 1c0ed34e927938e..0c53ad19a35747f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -10,6 +10,7 @@ import { Redirect } from 'react-router-dom'; import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { + DETECTION_ENGINE_PAGE_NAME, getDetectionEngineUrl, getCreateRuleUrl, } from '../../../components/link_to/redirect_to_detection_engine'; @@ -22,7 +23,7 @@ import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; import { ReadOnlyCallOut } from './components/read_only_callout'; import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/update_callout'; -import { getPrePackagedRuleStatus } from './helpers'; +import { getPrePackagedRuleStatus, redirectToDetections } from './helpers'; import * as i18n from './translations'; type Func = () => void; @@ -35,6 +36,7 @@ const RulesPageComponent: React.FC = () => { loading, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, canUserCRUD, hasIndexWrite, hasManageApiKey, @@ -54,6 +56,7 @@ const RulesPageComponent: React.FC = () => { hasManageApiKey, isSignalIndexExists, isAuthenticated, + hasEncryptionKey, }); const prePackagedRuleStatus = getPrePackagedRuleStatus( rulesInstalled, @@ -83,12 +86,8 @@ const RulesPageComponent: React.FC = () => { refreshRulesData.current = refreshRule; }, []); - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { + return ; } return ( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index eea25a1e89cc847..6a42aed123fa3ce 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -390,6 +390,7 @@ export const getMockPrivileges = () => ({ }, application: {}, is_authenticated: false, + has_encryption_key: true, }); export const getFindResultStatus = (): SavedObjectsFindResponse => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index 803d9d645aadbec..5ea4dc7595b2b11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -29,8 +29,10 @@ export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.Serve const callWithRequest = callWithRequestFactory(request, server); const index = getIndex(request, server); const permissions = await readPrivileges(callWithRequest, index); + const usingEphemeralEncryptionKey = server.usingEphemeralEncryptionKey; return merge(permissions, { is_authenticated: request?.auth?.isAuthenticated ?? false, + has_encryption_key: !usingEphemeralEncryptionKey, }); } catch (err) { return transformError(err); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts index b9ff2e601862427..ce6246934288372 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts @@ -36,28 +36,32 @@ export const createExportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = return headers.response().code(404); } - const exportSizeLimit = server.config().get('savedObjects.maxImportExportSize'); - if (request.payload?.objects != null && request.payload.objects.length > exportSizeLimit) { - return Boom.badRequest(`Can't export more than ${exportSizeLimit} rules`); - } else { - const nonPackagedRulesCount = await getNonPackagedRulesCount({ alertsClient }); - if (nonPackagedRulesCount > exportSizeLimit) { + try { + const exportSizeLimit = server.config().get('savedObjects.maxImportExportSize'); + if (request.payload?.objects != null && request.payload.objects.length > exportSizeLimit) { return Boom.badRequest(`Can't export more than ${exportSizeLimit} rules`); + } else { + const nonPackagedRulesCount = await getNonPackagedRulesCount({ alertsClient }); + if (nonPackagedRulesCount > exportSizeLimit) { + return Boom.badRequest(`Can't export more than ${exportSizeLimit} rules`); + } } - } - const exported = - request.payload?.objects != null - ? await getExportByObjectIds(alertsClient, request.payload.objects) - : await getExportAll(alertsClient); + const exported = + request.payload?.objects != null + ? await getExportByObjectIds(alertsClient, request.payload.objects) + : await getExportAll(alertsClient); - const response = request.query.exclude_export_details - ? headers.response(exported.rulesNdjson) - : headers.response(`${exported.rulesNdjson}${exported.exportDetails}`); + const response = request.query.exclude_export_details + ? headers.response(exported.rulesNdjson) + : headers.response(`${exported.rulesNdjson}${exported.exportDetails}`); - return response - .header('Content-Disposition', `attachment; filename="${request.query.file_name}"`) - .header('Content-Type', 'application/ndjson'); + return response + .header('Content-Disposition', `attachment; filename="${request.query.file_name}"`) + .header('Content-Type', 'application/ndjson'); + } catch { + return Boom.badRequest(`Sorry, something went wrong to export rules`); + } }, }; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 71fdef3623bc71a..0d57f5739fc15a9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -6,8 +6,9 @@ import Boom from 'boom'; import Hapi from 'hapi'; +import { chunk, isEmpty, isFunction } from 'lodash/fp'; import { extname } from 'path'; -import { isFunction } from 'lodash/fp'; +import uuid from 'uuid'; import { createPromiseFromStreams } from '../../../../../../../../../src/legacy/utils/streams'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { createRules } from '../../rules/create_rules'; @@ -18,17 +19,25 @@ import { getIndexExists } from '../../index/get_index_exists'; import { callWithRequestFactory, getIndex, - createImportErrorObject, - transformImportError, - ImportSuccessError, + createBulkErrorObject, + ImportRuleResponse, } from '../utils'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { ImportRuleAlertRest } from '../../types'; -import { transformOrImportError } from './utils'; import { updateRules } from '../../rules/update_rules'; import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; import { KibanaRequest } from '../../../../../../../../../src/core/server'; +type PromiseFromStreams = ImportRuleAlertRest | Error; + +/* + * We were getting some error like that possible EventEmitter memory leak detected + * So we decide to batch the update by 10 to avoid any complication in the node side + * https://nodejs.org/docs/latest/api/events.html#events_emitter_setmaxlisteners_n + * + */ +const CHUNK_PARSED_OBJECT_SIZE = 10; + export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { method: 'POST', @@ -67,145 +76,189 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = const objectLimit = server.config().get('savedObjects.maxImportExportSize'); const readStream = createRulesStreamFromNdJson(request.payload.file, objectLimit); - const parsedObjects = await createPromiseFromStreams<[ImportRuleAlertRest | Error]>([ - readStream, - ]); - - const reduced = await parsedObjects.reduce>( - async (accum, parsedRule) => { - const existingImportSuccessError = await accum; - if (parsedRule instanceof Error) { - // If the JSON object had a validation or parse error then we return - // early with the error and an (unknown) for the ruleId - return createImportErrorObject({ - ruleId: '(unknown)', // TODO: Better handling where we know which ruleId is having issues with imports - statusCode: 400, - message: parsedRule.message, - existingImportSuccessError, - }); - } + const parsedObjects = await createPromiseFromStreams([readStream]); - const { - description, - enabled, - false_positives: falsePositives, - from, - immutable, - query, - language, - output_index: outputIndex, - saved_id: savedId, - meta, - filters, - rule_id: ruleId, - index, - interval, - max_signals: maxSignals, - risk_score: riskScore, - name, - severity, - tags, - threat, - to, - type, - references, - timeline_id: timelineId, - timeline_title: timelineTitle, - version, - } = parsedRule; - try { - const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); - const callWithRequest = callWithRequestFactory(request, server); - const indexExists = await getIndexExists(callWithRequest, finalIndex); - if (!indexExists) { - return createImportErrorObject({ - ruleId, - statusCode: 409, - message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, - existingImportSuccessError, - }); - } - const rule = await readRules({ alertsClient, ruleId }); - if (rule == null) { - const createdRule = await createRules({ - alertsClient, - actionsClient, - description, - enabled, - falsePositives, - from, - immutable, - query, - language, - outputIndex: finalIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threat, - references, - version, - }); - return transformOrImportError(ruleId, createdRule, existingImportSuccessError); - } else if (rule != null && request.query.overwrite) { - const updatedRule = await updateRules({ - alertsClient, - actionsClient, - savedObjectsClient, - description, - enabled, - falsePositives, - from, - immutable, - query, - language, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - id: undefined, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threat, - references, - version, - }); - return transformOrImportError(ruleId, updatedRule, existingImportSuccessError); - } else { - return existingImportSuccessError; - } - } catch (err) { - return transformImportError(ruleId, err, existingImportSuccessError); - } - }, - Promise.resolve({ - success: true, - success_count: 0, - errors: [], - }) + const uniqueParsedObjects = Array.from( + parsedObjects + .reduce( + (acc, parsedRule) => { + if (parsedRule instanceof Error) { + acc.set(uuid.v4(), parsedRule); + } else { + const { rule_id: ruleId } = parsedRule; + if (ruleId != null) { + acc.set(ruleId, parsedRule); + } else { + acc.set(uuid.v4(), parsedRule); + } + } + return acc; + }, // using map (preserves ordering) + new Map() + ) + .values() ); - return reduced; + + const chunkParseObjects = chunk(CHUNK_PARSED_OBJECT_SIZE, uniqueParsedObjects); + let importRuleResponse: ImportRuleResponse[] = []; + + while (chunkParseObjects.length) { + const batchParseObjects = chunkParseObjects.shift() ?? []; + const newImportRuleResponse = await Promise.all( + batchParseObjects.reduce>>((accum, parsedRule) => { + const importsWorkerPromise = new Promise( + async (resolve, reject) => { + if (parsedRule instanceof Error) { + // If the JSON object had a validation or parse error then we return + // early with the error and an (unknown) for the ruleId + resolve( + createBulkErrorObject({ + ruleId: '(unknown)', + statusCode: 400, + message: parsedRule.message, + }) + ); + return null; + } + const { + description, + false_positives: falsePositives, + from, + immutable, + query, + language, + output_index: outputIndex, + saved_id: savedId, + meta, + filters, + rule_id: ruleId, + index, + interval, + max_signals: maxSignals, + risk_score: riskScore, + name, + severity, + tags, + threat, + to, + type, + references, + timeline_id: timelineId, + timeline_title: timelineTitle, + version, + } = parsedRule; + try { + const finalIndex = getIndex(request, server); + const callWithRequest = callWithRequestFactory(request, server); + const indexExists = await getIndexExists(callWithRequest, finalIndex); + if (!indexExists) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, + }) + ); + } + const rule = await readRules({ alertsClient, ruleId }); + if (rule == null) { + await createRules({ + alertsClient, + actionsClient, + description, + enabled: false, + falsePositives, + from, + immutable, + query, + language, + outputIndex: finalIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threat, + references, + version, + }); + resolve({ rule_id: ruleId, status_code: 200 }); + } else if (rule != null && request.query.overwrite) { + await updateRules({ + alertsClient, + actionsClient, + savedObjectsClient, + description, + enabled: false, + falsePositives, + from, + immutable, + query, + language, + outputIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + id: undefined, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threat, + references, + version, + }); + resolve({ rule_id: ruleId, status_code: 200 }); + } else if (rule != null) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `This Rule "${rule.name}" already exists`, + }) + ); + } + } catch (err) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 400, + message: err.message, + }) + ); + } + } + ); + return [...accum, importsWorkerPromise]; + }, []) + ); + importRuleResponse = [...importRuleResponse, ...newImportRuleResponse]; + } + + const errorsResp = importRuleResponse.filter(resp => !isEmpty(resp.error)); + return { + success: errorsResp.length === 0, + success_count: importRuleResponse.filter(resp => resp.status_code === 200).length, + errors: errorsResp, + }; }, }; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index f51cea0753f1abd..590307e06a26a9f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -39,8 +39,8 @@ export const createUpdateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = language, output_index: outputIndex, saved_id: savedId, - timeline_id: timelineId, - timeline_title: timelineTitle, + timeline_id: timelineId = null, + timeline_title: timelineTitle = null, meta, filters, rule_id: ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 19cd972b60e1a8d..416c76b5d4eb5d5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -52,6 +52,16 @@ export const createBulkErrorObject = ({ }; }; +export interface ImportRuleResponse { + rule_id: string; + status_code?: number; + message?: string; + error?: { + status_code: number; + message: string; + }; +} + export interface ImportSuccessError { success: boolean; success_count: number; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 05e455efb3f22ef..236d04acc782b82 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -66,6 +66,7 @@ describe('get_export_by_object_ids', () => { const objects = [{ rule_id: 'rule-1' }]; const exports = await getRulesFromObjects(unsafeCast, objects); const expected: RulesErrors = { + exportedCount: 1, missingRules: [], rules: [ { @@ -141,6 +142,7 @@ describe('get_export_by_object_ids', () => { const objects = [{ rule_id: 'rule-1' }]; const exports = await getRulesFromObjects(unsafeCast, objects); const expected: RulesErrors = { + exportedCount: 0, missingRules: [{ rule_id: 'rule-1' }], rules: [], }; @@ -164,6 +166,7 @@ describe('get_export_by_object_ids', () => { const objects = [{ rule_id: 'rule-1' }]; const exports = await getRulesFromObjects(unsafeCast, objects); const expected: RulesErrors = { + exportedCount: 0, missingRules: [{ rule_id: 'rule-1' }], rules: [], }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts index a5cf1bbfb78588b..7e0d61d040617ec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -11,7 +11,20 @@ import { readRules } from './read_rules'; import { transformRulesToNdjson, transformAlertToRule } from '../routes/rules/utils'; import { OutputRuleAlertRest } from '../types'; +interface ExportSuccesRule { + statusCode: 200; + rule: Partial; +} + +interface ExportFailedRule { + statusCode: 404; + missingRuleId: { rule_id: string }; +} + +type ExportRules = ExportSuccesRule | ExportFailedRule; + export interface RulesErrors { + exportedCount: number; missingRules: Array<{ rule_id: string }>; rules: Array>; } @@ -33,28 +46,44 @@ export const getRulesFromObjects = async ( alertsClient: AlertsClient, objects: Array<{ rule_id: string }> ): Promise => { - const alertsAndErrors = await objects.reduce>( - async (accumPromise, object) => { - const accum = await accumPromise; - const rule = await readRules({ alertsClient, ruleId: object.rule_id }); - if (rule != null && isAlertType(rule) && rule.params.immutable !== true) { - const transformedRule = transformAlertToRule(rule); - return { - missingRules: accum.missingRules, - rules: [...accum.rules, transformedRule], - }; - } else { - return { - missingRules: [...accum.missingRules, { rule_id: object.rule_id }], - rules: accum.rules, - }; - } - }, - Promise.resolve({ - exportedCount: 0, - missingRules: [], - rules: [], - }) + const alertsAndErrors = await Promise.all( + objects.reduce>>((accumPromise, object) => { + const exportWorkerPromise = new Promise(async resolve => { + try { + const rule = await readRules({ alertsClient, ruleId: object.rule_id }); + if (rule != null && isAlertType(rule) && rule.params.immutable !== true) { + const transformedRule = transformAlertToRule(rule); + resolve({ + statusCode: 200, + rule: transformedRule, + }); + } else { + resolve({ + statusCode: 404, + missingRuleId: { rule_id: object.rule_id }, + }); + } + } catch { + resolve({ + statusCode: 404, + missingRuleId: { rule_id: object.rule_id }, + }); + } + }); + return [...accumPromise, exportWorkerPromise]; + }, []) ); - return alertsAndErrors; + + const missingRules = alertsAndErrors.filter( + resp => resp.statusCode === 404 + ) as ExportFailedRule[]; + const exportedRules = alertsAndErrors.filter( + resp => resp.statusCode === 200 + ) as ExportSuccesRule[]; + + return { + exportedCount: exportedRules.length, + missingRules: missingRules.map(mr => mr.missingRuleId), + rules: exportedRules.map(er => er.rule), + }; }; diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts index 3fa2268afe92c3d..7c07e63404eaa69 100644 --- a/x-pack/legacy/plugins/siem/server/types.ts +++ b/x-pack/legacy/plugins/siem/server/types.ts @@ -8,6 +8,7 @@ import { Legacy } from 'kibana'; export interface ServerFacade { config: Legacy.Server['config']; + usingEphemeralEncryptionKey: boolean; plugins: { // eslint-disable-next-line @typescript-eslint/no-explicit-any actions: any; // We have to do this at the moment because the types are not compatible diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts index b46cecdc762b752..a5877f6c34b8f20 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts @@ -3132,7 +3132,7 @@ export const filebeatSchema: Schema = [ { name: 'user.roles', description: 'Roles to which the principal belongs', - example: ['kibana_user', 'beats_admin'], + example: ['kibana_admin', 'beats_admin'], type: 'keyword', }, { diff --git a/x-pack/legacy/plugins/transform/common/constants.ts b/x-pack/legacy/plugins/transform/common/constants.ts index c85408d3c5ce60b..39138c12c8299e4 100644 --- a/x-pack/legacy/plugins/transform/common/constants.ts +++ b/x-pack/legacy/plugins/transform/common/constants.ts @@ -39,11 +39,11 @@ export const API_BASE_PATH = '/api/transform/'; // - dest index: index, create_index (can be applied to a pattern e.g. df-*) // // In the UI additional privileges are required: -// - kibana_user (builtin) +// - kibana_admin (builtin) // - dest index: monitor (applied to df-*) // - cluster: monitor // -// Note that users with kibana_user can see all Kibana index patterns and saved searches +// Note that users with kibana_admin can see all Kibana index patterns and saved searches // in the source selection modal when creating a transform, but the wizard will trigger // error callouts when there are no sufficient privileges to read the actual source indices. diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts index c1382d455313f0b..f01448d9e37acdd 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts @@ -84,7 +84,8 @@ export const getSupportedUrlParams = (params: { ), absoluteDateRangeEnd: parseAbsoluteDate( dateRangeEnd || DATE_RANGE_END, - ABSOLUTE_DATE_RANGE_END + ABSOLUTE_DATE_RANGE_END, + { roundUp: true } ), autorefreshInterval: parseUrlInt(autorefreshInterval, AUTOREFRESH_INTERVAL), autorefreshIsPaused: parseIsPaused(autorefreshIsPaused, AUTOREFRESH_IS_PAUSED), diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/parse_absolute_date.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/parse_absolute_date.ts index eaf720a8d2f7e1b..2b0921f07abc975 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/parse_absolute_date.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/parse_absolute_date.ts @@ -6,8 +6,8 @@ import DateMath from '@elastic/datemath'; -export const parseAbsoluteDate = (date: string, defaultValue: number): number => { - const momentWrapper = DateMath.parse(date); +export const parseAbsoluteDate = (date: string, defaultValue: number, options = {}): number => { + const momentWrapper = DateMath.parse(date, options); if (momentWrapper) { return momentWrapper.valueOf(); } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts index e34bc6ab805c064..634d6369531d8cb 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts @@ -25,7 +25,6 @@ export const findPotentialMatches = async ( size: number ) => { const queryResult = await query(queryContext, searchAfter, size); - const checkGroups = new Set(); const monitorIds: string[] = []; get(queryResult, 'aggregations.monitors.buckets', []).forEach((b: any) => { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts index d97b7653402a323..961cc94dcea19fb 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import DateMath from '@elastic/datemath'; import { APICaller } from 'kibana/server'; import { CursorPagination } from '../adapter_types'; import { INDEX_NAMES } from '../../../../../common/constants'; +import { parseRelativeDate } from '../../../helper/get_histogram_interval'; export class QueryContext { callES: APICaller; @@ -95,8 +95,9 @@ export class QueryContext { // latencies and slowdowns that's dangerous. Making this value larger makes things // only slower, but only marginally so, and prevents people from seeing weird // behavior. - const tsStart = DateMath.parse(this.dateRangeEnd)!.subtract(5, 'minutes'); - const tsEnd = DateMath.parse(this.dateRangeEnd)!; + + const tsEnd = parseRelativeDate(this.dateRangeEnd, { roundUp: true })!; + const tsStart = tsEnd.subtract(5, 'minutes'); return { range: { diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts new file mode 100644 index 000000000000000..ec6e48c62c62e0c --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parseRelativeDate } from '../get_histogram_interval'; +import { Moment } from 'moment'; + +describe('Parsing a relative end date properly', () => { + it('converts the upper range of relative end dates to now', async () => { + const thisWeekEndDate = 'now/w'; + + let endDate = parseRelativeDate(thisWeekEndDate, { roundUp: true }); + expect(Date.now() - (endDate as Moment).valueOf()).toBeLessThan(1000); + + const todayEndDate = 'now/d'; + + endDate = parseRelativeDate(todayEndDate, { roundUp: true }); + + expect(Date.now() - (endDate as Moment).valueOf()).toBeLessThan(1000); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts index 0dedc3e456f5132..26515fb4b4c63b9 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts @@ -7,18 +7,39 @@ import DateMath from '@elastic/datemath'; import { QUERY } from '../../../common/constants'; +export const parseRelativeDate = (dateStr: string, options = {}) => { + // We need this this parsing because if user selects This week or this date + // That represents end date in future, if week or day is still in the middle + // Uptime data can never be collected in future, so we will reset date to now + // in That case. Example case we select this week range will be to='now/w' and from = 'now/w'; + + const parsedDate = DateMath.parse(dateStr, options); + const dateTimestamp = parsedDate?.valueOf() ?? 0; + if (dateTimestamp > Date.now()) { + return DateMath.parse('now'); + } + return parsedDate; +}; + export const getHistogramInterval = ( dateRangeStart: string, dateRangeEnd: string, bucketCount?: number ): number => { - const from = DateMath.parse(dateRangeStart); - const to = DateMath.parse(dateRangeEnd); + const from = parseRelativeDate(dateRangeStart); + + // roundUp is required for relative date like now/w to get the end of the week + const to = parseRelativeDate(dateRangeEnd, { roundUp: true }); if (from === undefined) { throw Error('Invalid dateRangeStart value'); } if (to === undefined) { throw Error('Invalid dateRangeEnd value'); } - return Math.round((to.valueOf() - from.valueOf()) / (bucketCount || QUERY.DEFAULT_BUCKET_COUNT)); + const interval = Math.round( + (to.valueOf() - from.valueOf()) / (bucketCount || QUERY.DEFAULT_BUCKET_COUNT) + ); + + // Interval can never be zero, if it's 0 we return at least 1ms interval + return interval > 0 ? interval : 1; }; diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts index e9afab5680332d7..fce278e94bf32f6 100644 --- a/x-pack/plugins/canvas/server/routes/index.ts +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -5,9 +5,10 @@ */ import { IRouter, Logger } from 'src/core/server'; -import { initWorkpadRoutes } from './workpad'; import { initCustomElementsRoutes } from './custom_elements'; import { initESFieldsRoutes } from './es_fields'; +import { initShareablesRoutes } from './shareables'; +import { initWorkpadRoutes } from './workpad'; export interface RouteInitializerDeps { router: IRouter; @@ -15,7 +16,8 @@ export interface RouteInitializerDeps { } export function initRoutes(deps: RouteInitializerDeps) { - initWorkpadRoutes(deps); initCustomElementsRoutes(deps); initESFieldsRoutes(deps); + initShareablesRoutes(deps); + initWorkpadRoutes(deps); } diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts new file mode 100644 index 000000000000000..be4765217d7aa8e --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('fs'); + +import fs from 'fs'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { httpServiceMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { initializeDownloadShareableWorkpadRoute } from './download'; + +const mockRouteContext = {} as RequestHandlerContext; +const path = `api/canvas/workpad/find`; +const mockRuntime = 'Canvas shareable runtime'; + +describe('Download Canvas shareables runtime', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeDownloadShareableWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it(`returns 200 with canvas shareables runtime`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path, + }); + + const readFileSyncMock = fs.readFileSync as jest.Mock; + readFileSyncMock.mockReturnValueOnce(mockRuntime); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(`"Canvas shareable runtime"`); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.ts b/x-pack/plugins/canvas/server/routes/shareables/download.ts new file mode 100644 index 000000000000000..08bec1e4881aeb3 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/download.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { readFileSync } from 'fs'; +import { SHAREABLE_RUNTIME_FILE } from '../../../../../legacy/plugins/canvas/shareable_runtime/constants'; +import { RouteInitializerDeps } from '../'; +import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../../../../legacy/plugins/canvas/common/lib/constants'; + +export function initializeDownloadShareableWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.get( + { + path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, + validate: false, + }, + async (_context, _request, response) => { + // TODO: check if this is still an issue on cloud after migrating to NP + // + // The option setting is not for typical use. We're using it here to avoid + // problems in Cloud environments. See elastic/kibana#47405. + // @ts-ignore No type for inert Hapi handler + // const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false }); + const file = readFileSync(SHAREABLE_RUNTIME_FILE); + return response.ok({ + headers: { 'content-type': 'application/octet-stream' }, + body: file, + }); + } + ); +} diff --git a/x-pack/plugins/canvas/server/routes/shareables/index.ts b/x-pack/plugins/canvas/server/routes/shareables/index.ts new file mode 100644 index 000000000000000..0aabd8b955b2116 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteInitializerDeps } from '../'; +import { initializeZipShareableWorkpadRoute } from './zip'; +import { initializeDownloadShareableWorkpadRoute } from './download'; + +export function initShareablesRoutes(deps: RouteInitializerDeps) { + initializeDownloadShareableWorkpadRoute(deps); + initializeZipShareableWorkpadRoute(deps); +} diff --git a/x-pack/plugins/canvas/server/routes/shareables/mock_shareable_workpad.json b/x-pack/plugins/canvas/server/routes/shareables/mock_shareable_workpad.json new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/x-pack/plugins/canvas/server/routes/shareables/rendered_workpad_schema.ts b/x-pack/plugins/canvas/server/routes/shareables/rendered_workpad_schema.ts new file mode 100644 index 000000000000000..792200354724e47 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/rendered_workpad_schema.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const PositionSchema = schema.object({ + angle: schema.number(), + height: schema.number(), + left: schema.number(), + parent: schema.nullable(schema.string()), + top: schema.number(), + width: schema.number(), +}); + +export const ContainerStyleSchema = schema.object({ + type: schema.maybe(schema.string()), + border: schema.maybe(schema.string()), + borderRadius: schema.maybe(schema.string()), + padding: schema.maybe(schema.string()), + backgroundColor: schema.maybe(schema.string()), + backgroundImage: schema.maybe(schema.string()), + backgroundSize: schema.maybe(schema.string()), + backgroundRepeat: schema.maybe(schema.string()), + opacity: schema.maybe(schema.number()), + overflow: schema.maybe(schema.string()), +}); + +export const RenderableSchema = schema.object({ + error: schema.nullable(schema.string()), + state: schema.string(), + value: schema.object({ + as: schema.string(), + containerStyle: ContainerStyleSchema, + css: schema.maybe(schema.string()), + type: schema.string(), + value: schema.any(), + }), +}); + +export const RenderedWorkpadElementSchema = schema.object({ + expressionRenderable: RenderableSchema, + id: schema.string(), + position: PositionSchema, +}); + +export const RenderedWorkpadPageSchema = schema.object({ + id: schema.string(), + elements: schema.arrayOf(RenderedWorkpadElementSchema), + groups: schema.maybe(schema.arrayOf(schema.arrayOf(RenderedWorkpadElementSchema))), + style: schema.recordOf(schema.string(), schema.string()), + transition: schema.maybe( + schema.oneOf([ + schema.object({}), + schema.object({ + name: schema.string(), + }), + ]) + ), +}); + +export const RenderedWorkpadSchema = schema.object({ + '@created': schema.maybe(schema.string()), + '@timestamp': schema.maybe(schema.string()), + assets: schema.maybe(schema.recordOf(schema.string(), RenderedWorkpadPageSchema)), + colors: schema.arrayOf(schema.string()), + css: schema.string(), + height: schema.number(), + id: schema.string(), + isWriteable: schema.maybe(schema.boolean()), + name: schema.string(), + page: schema.number(), + pages: schema.arrayOf(RenderedWorkpadPageSchema), + width: schema.number(), +}); diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts new file mode 100644 index 000000000000000..edb59694a74005d --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('archiver'); + +const archiver = require('archiver') as jest.Mock; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { httpServiceMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { initializeZipShareableWorkpadRoute } from './zip'; +import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../legacy/plugins/canvas/common/lib'; +import { + SHAREABLE_RUNTIME_FILE, + SHAREABLE_RUNTIME_SRC, + SHAREABLE_RUNTIME_NAME, +} from '../../../../../legacy/plugins/canvas/shareable_runtime/constants'; + +const mockRouteContext = {} as RequestHandlerContext; +const mockWorkpad = {}; +const routePath = API_ROUTE_SHAREABLE_ZIP; + +describe('Zips Canvas shareables runtime together with workpad', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeZipShareableWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it(`returns 200 with zip file with runtime and workpad`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: routePath, + body: mockWorkpad, + }); + + const mockArchive = { + append: jest.fn(), + file: jest.fn(), + finalize: jest.fn(), + }; + + archiver.mockReturnValueOnce(mockArchive); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toBe(mockArchive); + expect(mockArchive.append).toHaveBeenCalledWith(JSON.stringify(mockWorkpad), { + name: 'workpad.json', + }); + expect(mockArchive.file).toHaveBeenCalledTimes(2); + expect(mockArchive.file).nthCalledWith(1, `${SHAREABLE_RUNTIME_SRC}/template.html`, { + name: 'index.html', + }); + expect(mockArchive.file).nthCalledWith(2, SHAREABLE_RUNTIME_FILE, { + name: `${SHAREABLE_RUNTIME_NAME}.js`, + }); + expect(mockArchive.finalize).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.ts new file mode 100644 index 000000000000000..e25b96cce96ff39 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import archiver from 'archiver'; +import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../legacy/plugins/canvas/common/lib'; +import { + SHAREABLE_RUNTIME_FILE, + SHAREABLE_RUNTIME_NAME, + SHAREABLE_RUNTIME_SRC, +} from '../../../../../legacy/plugins/canvas/shareable_runtime/constants'; +import { RenderedWorkpadSchema } from './rendered_workpad_schema'; +import { RouteInitializerDeps } from '..'; + +export function initializeZipShareableWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.post( + { + path: API_ROUTE_SHAREABLE_ZIP, + validate: { body: RenderedWorkpadSchema }, + }, + async (_context, request, response) => { + const workpad = request.body; + const archive = archiver('zip'); + archive.append(JSON.stringify(workpad), { name: 'workpad.json' }); + archive.file(`${SHAREABLE_RUNTIME_SRC}/template.html`, { name: 'index.html' }); + archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` }); + + const result = { headers: { 'content-type': 'application/zip' }, body: archive }; + archive.finalize(); + + return response.ok(result); + } + ); +} diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts index 7d6632aa56cb189..e05d8d687d05a3a 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts @@ -49,7 +49,7 @@ describe('config schema', () => { }); describe('createConfig$()', () => { - it('should log a warning and set xpack.encryptedSavedObjects.encryptionKey if not set', async () => { + it('should log a warning, set xpack.encryptedSavedObjects.encryptionKey and usingEphemeralEncryptionKey=true when encryptionKey is not set', async () => { const mockRandomBytes = jest.requireMock('crypto').randomBytes; mockRandomBytes.mockReturnValue('ab'.repeat(16)); @@ -57,7 +57,10 @@ describe('createConfig$()', () => { const config = await createConfig$(contextMock) .pipe(first()) .toPromise(); - expect(config).toEqual({ encryptionKey: 'ab'.repeat(16) }); + expect(config).toEqual({ + config: { encryptionKey: 'ab'.repeat(16) }, + usingEphemeralEncryptionKey: true, + }); expect(loggingServiceMock.collect(contextMock.logger).warn).toMatchInlineSnapshot(` Array [ @@ -67,4 +70,19 @@ describe('createConfig$()', () => { ] `); }); + + it('should not log a warning and set usingEphemeralEncryptionKey=false when encryptionKey is set', async () => { + const contextMock = coreMock.createPluginInitializerContext({ + encryptionKey: 'supersecret', + }); + const config = await createConfig$(contextMock) + .pipe(first()) + .toPromise(); + expect(config).toEqual({ + config: { encryptionKey: 'supersecret' }, + usingEphemeralEncryptionKey: false, + }); + + expect(loggingServiceMock.collect(contextMock.logger).warn).toEqual([]); + }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.ts b/x-pack/plugins/encrypted_saved_objects/server/config.ts index c755b7dd9f205f5..2f0185052072424 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/config.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/config.ts @@ -5,15 +5,10 @@ */ import crypto from 'crypto'; -import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; -export type ConfigType = ReturnType extends Observable - ? P - : ReturnType; - export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), encryptionKey: schema.conditional( @@ -30,6 +25,7 @@ export function createConfig$(context: PluginInitializerContext) { const logger = context.logger.get('config'); let encryptionKey = config.encryptionKey; + const usingEphemeralEncryptionKey = encryptionKey === undefined; if (encryptionKey === undefined) { logger.warn( 'Generating a random key for xpack.encryptedSavedObjects.encryptionKey. ' + @@ -40,7 +36,10 @@ export function createConfig$(context: PluginInitializerContext) { encryptionKey = crypto.randomBytes(16).toString('hex'); } - return { ...config, encryptionKey }; + return { + config: { ...config, encryptionKey }, + usingEphemeralEncryptionKey, + }; }) ); } diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index 87c36381a841aa6..7f53f47760f1295 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -10,6 +10,7 @@ function createEncryptedSavedObjectsSetupMock() { return { registerType: jest.fn(), __legacyCompat: { registerLegacyAPI: jest.fn() }, + usingEphemeralEncryptionKey: true, } as jest.Mocked; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts index 534ed13ba0acbdf..5228734e4a7732c 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts @@ -18,6 +18,7 @@ describe('EncryptedSavedObjects Plugin', () => { "registerLegacyAPI": [Function], }, "registerType": [Function], + "usingEphemeralEncryptionKey": true, } `); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index ecd917ff90d0039..d9185251ca46640 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -23,6 +23,7 @@ import { SavedObjectsSetup, setupSavedObjects } from './saved_objects'; export interface PluginSetupContract { registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void; __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => void }; + usingEphemeralEncryptionKey: boolean; } export interface PluginStartContract extends SavedObjectsSetup { @@ -59,7 +60,7 @@ export class Plugin { } public async setup(core: CoreSetup): Promise { - const config = await createConfig$(this.initializerContext) + const { config, usingEphemeralEncryptionKey } = await createConfig$(this.initializerContext) .pipe(first()) .toPromise(); @@ -81,6 +82,7 @@ export class Plugin { registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => service.registerType(typeRegistration), __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => (this.legacyAPI = legacyAPI) }, + usingEphemeralEncryptionKey, }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap index 07034aca18ec2d5..1fd565c7d07f3e9 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/space_aware_privilege_section.test.tsx.snap @@ -24,14 +24,14 @@ exports[` with user profile disabling "manageSpaces"

+ "kibanaAdmin": , diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index 21cadfafe1790b7..b2b92356e512678 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -95,13 +95,13 @@ class SpaceAwarePrivilegeSectionUI extends Component { ), diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 817aa03db31bd03..47e11817ffa5d40 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8908,7 +8908,7 @@ "xpack.ml.validateJob.validateJobButtonLabel": "ジョブを検証", "xpack.monitoring.accessDenied.backToKibanaButtonLabel": "Kibana に戻る", "xpack.monitoring.accessDenied.clusterNotConfiguredDescription": "専用の監視クラスターへのアクセスを試みている場合、監視クラスターで構成されていないユーザーとしてログインしていることが原因である可能性があります。", - "xpack.monitoring.accessDenied.notAuthorizedDescription": "監視アクセスが許可されていません。監視を利用するには、「{kibanaUser}」と「{monitoringUser}」の両方のロールからの権限が必要です。", + "xpack.monitoring.accessDenied.notAuthorizedDescription": "監視アクセスが許可されていません。監視を利用するには、「{kibanaAdmin}」と「{monitoringUser}」の両方のロールからの権限が必要です。", "xpack.monitoring.accessDeniedTitle": "アクセス拒否", "xpack.monitoring.ajaxErrorHandler.httpErrorMessage": "HTTP {errStatus}", "xpack.monitoring.ajaxErrorHandler.requestErrorNotificationTitle": "監視リクエストエラー", @@ -10644,11 +10644,11 @@ "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.spaceBasePrivilegeSource": "スペースベース権限", "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.spaceFeaturePrivilegeSource": "スペース機能権限", "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.unknownPrivilegeSource": "**不明**", - "xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "{kibanaUser} ロールによりアカウントにすべての権限が提供されていることを確認し、再試行してください。", + "xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "{kibanaAdmin} ロールによりアカウントにすべての権限が提供されていることを確認し、再試行してください。", "xpack.security.management.editRole.spaceAwarePrivilegeForm.globalSpacesName": "* グローバル (すべてのスペース)", "xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "利用可能なすべてのスペースを表示する権限がありません。", "xpack.security.management.editRole.spaceAwarePrivilegeForm.insufficientPrivilegesDescription": "権限が不十分です", - "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaUserTitle": "kibana_user", + "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDetails": "選択されたスペースの全機能への完全アクセスを許可します。", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDisplay": "すべて", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDropdownDisplay": "すべて", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d8012bbb526c99a..86d9a69dc0900b0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8907,7 +8907,7 @@ "xpack.ml.validateJob.validateJobButtonLabel": "验证作业", "xpack.monitoring.accessDenied.backToKibanaButtonLabel": "返回 Kibana", "xpack.monitoring.accessDenied.clusterNotConfiguredDescription": "如果您尝试访问专用监测集群,则这可能是因为该监测集群上未配置您登录时所用的用户帐户。", - "xpack.monitoring.accessDenied.notAuthorizedDescription": "您无权访问 Monitoring。要使用 Monitoring,您同时需要 `{kibanaUser}` 和 `{monitoringUser}` 角色授予的权限。", + "xpack.monitoring.accessDenied.notAuthorizedDescription": "您无权访问 Monitoring。要使用 Monitoring,您同时需要 `{kibanaAdmin}` 和 `{monitoringUser}` 角色授予的权限。", "xpack.monitoring.accessDeniedTitle": "访问被拒绝", "xpack.monitoring.ajaxErrorHandler.httpErrorMessage": "HTTP {errStatus}", "xpack.monitoring.ajaxErrorHandler.requestErrorNotificationTitle": "Monitoring 请求错误", @@ -10643,11 +10643,11 @@ "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.spaceBasePrivilegeSource": "工作区基本权限", "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.spaceFeaturePrivilegeSource": "全局功能权限", "xpack.security.management.editRole.spaceAwarePrivilegeDisplay.unknownPrivilegeSource": "**未知**", - "xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "请确保您的帐户具有 {kibanaUser} 角色授予的所有权限,然后重试。", + "xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "请确保您的帐户具有 {kibanaAdmin} 角色授予的所有权限,然后重试。", "xpack.security.management.editRole.spaceAwarePrivilegeForm.globalSpacesName": "* 全局(所有工作区)", "xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "您无权查看所有可用工作区。", "xpack.security.management.editRole.spaceAwarePrivilegeForm.insufficientPrivilegesDescription": "权限不足", - "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaUserTitle": "kibana_user", + "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDetails": "授予对选定工作区所有功能的完全访问权限。", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDisplay": "全部", "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDropdownDisplay": "全部", diff --git a/x-pack/test/api_integration/apis/console/feature_controls.ts b/x-pack/test/api_integration/apis/console/feature_controls.ts index 3f9a08677943781..ce926f0d032c8bf 100644 --- a/x-pack/test/api_integration/apis/console/feature_controls.ts +++ b/x-pack/test/api_integration/apis/console/feature_controls.ts @@ -43,6 +43,29 @@ export default function securityTests({ getService }: FtrProviderContext) { } }); + it('can be accessed by kibana_admin role', async () => { + const username = 'kibana_admin'; + const roleName = 'kibana_admin'; + try { + const password = `${username}-password`; + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana admin', + }); + + await supertest + .post(`/api/console/proxy?method=GET&path=${encodeURIComponent('/_cat')}`) + .auth(username, password) + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + } finally { + await security.user.delete(username); + } + }); + it('can be accessed by global all role', async () => { const username = 'global_all'; const roleName = 'global_all'; diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/security.js b/x-pack/test/api_integration/apis/monitoring/setup/collection/security.js index 7e6a2dbe3196590..4da08d7cb972650 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/security.js +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/security.js @@ -44,7 +44,7 @@ export default function({ getService }) { await security.user.create(username, { password: password, full_name: 'Limited User', - roles: ['kibana_user', 'monitoring_user'], + roles: ['kibana_admin', 'monitoring_user'], }); const { body } = await supertestWithoutAuth diff --git a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts index db5e11ef367ad73..35a6f2c2b382a6a 100644 --- a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts +++ b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts @@ -12,8 +12,8 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const security = getService('security'); describe('feature controls', () => { - const kibanaUsername = 'kibana_user'; - const kibanaUserRoleName = 'kibana_user'; + const kibanaUsername = 'kibana_admin'; + const kibanaUserRoleName = 'kibana_admin'; const kibanaUserPassword = `${kibanaUsername}-password`; diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js index 1189fe909ca3203..b521c47585d58b6 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js @@ -92,7 +92,7 @@ export default function({ getService, getPageObjects }) { await testSubjects.setValue('userFormFullNameInput', 'mixeduser'); await testSubjects.setValue('userFormEmailInput', 'example@example.com'); await PageObjects.security.assignRoleToUser('kibana_dashboard_only_user'); - await PageObjects.security.assignRoleToUser('kibana_user'); + await PageObjects.security.assignRoleToUser('kibana_admin'); await PageObjects.security.assignRoleToUser('logstash-data'); await PageObjects.security.clickSaveEditUser(); diff --git a/x-pack/test/functional/apps/security/doc_level_security_roles.js b/x-pack/test/functional/apps/security/doc_level_security_roles.js index 5761369f9e4685b..480fa6599e03652 100644 --- a/x-pack/test/functional/apps/security/doc_level_security_roles.js +++ b/x-pack/test/functional/apps/security/doc_level_security_roles.js @@ -58,11 +58,11 @@ export default function({ getService, getPageObjects }) { fullname: 'dls EAST', email: 'dlstest@elastic.com', save: true, - roles: ['kibana_user', 'myroleEast'], + roles: ['kibana_admin', 'myroleEast'], }); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); - expect(users.userEast.roles).to.eql(['kibana_user', 'myroleEast']); + expect(users.userEast.roles).to.eql(['kibana_admin', 'myroleEast']); expect(users.userEast.reserved).to.be(false); }); diff --git a/x-pack/test/functional/apps/security/field_level_security.js b/x-pack/test/functional/apps/security/field_level_security.js index 16e9d755bf261b4..93a6f9cd9e0c3d8 100644 --- a/x-pack/test/functional/apps/security/field_level_security.js +++ b/x-pack/test/functional/apps/security/field_level_security.js @@ -79,11 +79,11 @@ export default function({ getService, getPageObjects }) { fullname: 'customer one', email: 'flstest@elastic.com', save: true, - roles: ['kibana_user', 'a_viewssnrole'], + roles: ['kibana_admin', 'a_viewssnrole'], }); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); - expect(users.customer1.roles).to.eql(['kibana_user', 'a_viewssnrole']); + expect(users.customer1.roles).to.eql(['kibana_admin', 'a_viewssnrole']); }); it('should add new user customer2 ', async function() { @@ -95,11 +95,11 @@ export default function({ getService, getPageObjects }) { fullname: 'customer two', email: 'flstest@elastic.com', save: true, - roles: ['kibana_user', 'a_view_no_ssn_role'], + roles: ['kibana_admin', 'a_view_no_ssn_role'], }); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); - expect(users.customer2.roles).to.eql(['kibana_user', 'a_view_no_ssn_role']); + expect(users.customer2.roles).to.eql(['kibana_admin', 'a_view_no_ssn_role']); }); it('user customer1 should see ssn', async function() { diff --git a/x-pack/test/functional/apps/security/secure_roles_perm.js b/x-pack/test/functional/apps/security/secure_roles_perm.js index ece289b4a666eea..4e155872d1041ae 100644 --- a/x-pack/test/functional/apps/security/secure_roles_perm.js +++ b/x-pack/test/functional/apps/security/secure_roles_perm.js @@ -61,13 +61,13 @@ export default function({ getService, getPageObjects }) { fullname: 'RashmiFirst RashmiLast', email: 'rashmi@myEmail.com', save: true, - roles: ['logstash_reader', 'kibana_user'], + roles: ['logstash_reader', 'kibana_admin'], }); log.debug('After Add user: , userObj.userName'); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); log.debug('roles: ', users.Rashmi.roles); - expect(users.Rashmi.roles).to.eql(['logstash_reader', 'kibana_user']); + expect(users.Rashmi.roles).to.eql(['logstash_reader', 'kibana_admin']); expect(users.Rashmi.fullname).to.eql('RashmiFirst RashmiLast'); expect(users.Rashmi.reserved).to.be(false); await PageObjects.security.forceLogout(); diff --git a/x-pack/test/functional/apps/security/user_email.js b/x-pack/test/functional/apps/security/user_email.js index 492eddcfb9f7466..a007c40a06b6261 100644 --- a/x-pack/test/functional/apps/security/user_email.js +++ b/x-pack/test/functional/apps/security/user_email.js @@ -27,11 +27,11 @@ export default function({ getService, getPageObjects }) { fullname: 'newuserFirst newuserLast', email: 'newuser@myEmail.com', save: true, - roles: ['kibana_user', 'superuser'], + roles: ['kibana_admin', 'superuser'], }); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); - expect(users.newuser.roles).to.eql(['kibana_user', 'superuser']); + expect(users.newuser.roles).to.eql(['kibana_admin', 'superuser']); expect(users.newuser.fullname).to.eql('newuserFirst newuserLast'); expect(users.newuser.email).to.eql('newuser@myEmail.com'); expect(users.newuser.reserved).to.be(false); diff --git a/x-pack/test/functional/apps/security/users.js b/x-pack/test/functional/apps/security/users.js index 3eed74881e95718..9dc42553f0fdfee 100644 --- a/x-pack/test/functional/apps/security/users.js +++ b/x-pack/test/functional/apps/security/users.js @@ -42,11 +42,11 @@ export default function({ getService, getPageObjects }) { fullname: 'LeeFirst LeeLast', email: 'lee@myEmail.com', save: true, - roles: ['kibana_user'], + roles: ['kibana_admin'], }); const users = indexBy(await PageObjects.security.getElasticsearchUsers(), 'username'); log.debug('actualUsers = %j', users); - expect(users.Lee.roles).to.eql(['kibana_user']); + expect(users.Lee.roles).to.eql(['kibana_admin']); expect(users.Lee.fullname).to.eql('LeeFirst LeeLast'); expect(users.Lee.email).to.eql('lee@myEmail.com'); expect(users.Lee.reserved).to.be(false); @@ -85,7 +85,7 @@ export default function({ getService, getPageObjects }) { expect(roles.apm_user.reserved).to.be(true); expect(roles.beats_admin.reserved).to.be(true); expect(roles.beats_system.reserved).to.be(true); - expect(roles.kibana_user.reserved).to.be(true); + expect(roles.kibana_admin.reserved).to.be(true); expect(roles.kibana_system.reserved).to.be(true); expect(roles.logstash_system.reserved).to.be(true); expect(roles.monitoring_user.reserved).to.be(true); diff --git a/x-pack/test/functional/page_objects/monitoring_page.js b/x-pack/test/functional/page_objects/monitoring_page.js index 6920010d67187f4..8de5b5e69d34d7e 100644 --- a/x-pack/test/functional/page_objects/monitoring_page.js +++ b/x-pack/test/functional/page_objects/monitoring_page.js @@ -14,7 +14,7 @@ export function MonitoringPageProvider({ getPageObjects, getService }) { // always create this because our tear down tries to delete it await security.user.create('basic_monitoring_user', { password: 'monitoring_user_password', - roles: ['monitoring_user', 'kibana_user'], + roles: ['monitoring_user', 'kibana_admin'], full_name: 'basic monitoring', }); diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 0346da334d2f2d0..203f90c55aa82b7 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -38,7 +38,7 @@ export default function({ getService }: FtrProviderContext) { await getService('esSupertest') .post('/_security/role_mapping/krb5') .send({ - roles: ['kibana_user'], + roles: ['kibana_admin'], enabled: true, rules: { field: { 'realm.name': 'kerb1' } }, }) @@ -119,7 +119,7 @@ export default function({ getService }: FtrProviderContext) { .set('Cookie', sessionCookie.cookieString()) .expect(200, { username: 'tester@TEST.ELASTIC.CO', - roles: ['kibana_user'], + roles: ['kibana_admin'], full_name: null, email: null, metadata: { diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts index 4eee900e68bec99..186ed824b3b6c7d 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts @@ -48,7 +48,7 @@ export default function({ getService }: FtrProviderContext) { .post('/_security/role_mapping/first_client_pki') .ca(CA_CERT) .send({ - roles: ['kibana_user'], + roles: ['kibana_admin'], enabled: true, rules: { field: { dn: 'CN=first_client' } }, }) @@ -107,7 +107,7 @@ export default function({ getService }: FtrProviderContext) { expect(response.body).to.eql({ username: 'first_client', - roles: ['kibana_user'], + roles: ['kibana_admin'], full_name: null, email: null, enabled: true, diff --git a/x-pack/test_utils/kbn_server_config.ts b/x-pack/test_utils/kbn_server_config.ts index 75f5ac736b7c073..3cac6ed5df01474 100644 --- a/x-pack/test_utils/kbn_server_config.ts +++ b/x-pack/test_utils/kbn_server_config.ts @@ -26,9 +26,9 @@ export const TestKbnServerConfig = { }, users: [ { - username: 'kibana_user', + username: 'kibana_admin', password: 'x-pack-test-password', - roles: ['kibana_user'], + roles: ['kibana_admin'], }, ], };