From 2ff83274283b0596969dfe29c49ce13892f29102 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 3 Aug 2021 15:52:11 +0200 Subject: [PATCH] Implement `interactiveSetup` plugin server side functionality: `setup` layout (#105222) --- .eslintrc.js | 1 + .github/CODEOWNERS | 4 +- .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 8 +- packages/kbn-optimizer/limits.yml | 2 +- .../README.md | 2 +- .../common/elasticsearch_connection_status.ts | 27 ++++ .../common/index.ts} | 11 +- src/plugins/interactive_setup/common/types.ts | 45 +++++++ .../jest.config.js | 2 +- .../kibana.json | 5 +- .../public/app.tsx | 0 .../public/index.ts | 0 .../public/plugin.tsx | 5 +- .../server/config.ts | 0 .../server/index.ts | 10 +- .../interactive_setup/server/plugin.ts | 119 ++++++++++++++++++ .../interactive_setup/server/routes/enroll.ts | 31 +++++ .../interactive_setup/server/routes/index.ts | 28 +++++ .../tsconfig.json | 2 +- tsconfig.json | 1 + tsconfig.refs.json | 1 + 22 files changed, 280 insertions(+), 25 deletions(-) rename src/plugins/{user_setup => interactive_setup}/README.md (69%) create mode 100644 src/plugins/interactive_setup/common/elasticsearch_connection_status.ts rename src/plugins/{user_setup/server/plugin.ts => interactive_setup/common/index.ts} (62%) create mode 100644 src/plugins/interactive_setup/common/types.ts rename src/plugins/{user_setup => interactive_setup}/jest.config.js (88%) rename src/plugins/{user_setup => interactive_setup}/kibana.json (74%) rename src/plugins/{user_setup => interactive_setup}/public/app.tsx (100%) rename src/plugins/{user_setup => interactive_setup}/public/index.ts (100%) rename src/plugins/{user_setup => interactive_setup}/public/plugin.tsx (92%) rename src/plugins/{user_setup => interactive_setup}/server/config.ts (100%) rename src/plugins/{user_setup => interactive_setup}/server/index.ts (69%) create mode 100644 src/plugins/interactive_setup/server/plugin.ts create mode 100644 src/plugins/interactive_setup/server/routes/enroll.ts create mode 100644 src/plugins/interactive_setup/server/routes/index.ts rename src/plugins/{user_setup => interactive_setup}/tsconfig.json (81%) diff --git a/.eslintrc.js b/.eslintrc.js index 9e0e749e74e51..a57ea5e03865a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1573,6 +1573,7 @@ module.exports = { files: [ 'src/plugins/security_oss/**/*.{js,mjs,ts,tsx}', 'src/plugins/spaces_oss/**/*.{js,mjs,ts,tsx}', + 'src/plugins/interactive_setup/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/encrypted_saved_objects/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/spaces/**/*.{js,mjs,ts,tsx}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0744112650c23..d2462626c2986 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -252,7 +252,7 @@ /src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core /src/plugins/security_oss/ @elastic/kibana-security /src/plugins/spaces_oss/ @elastic/kibana-security -/src/plugins/user_setup/ @elastic/kibana-security +/src/plugins/interactive_setup/ @elastic/kibana-security /test/security_functional/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security @@ -351,7 +351,7 @@ /x-pack/test/case_api_integration @elastic/security-threat-hunting /x-pack/plugins/lists @elastic/security-detections-response -## Security Solution sub teams - security-onboarding-and-lifecycle-mgt +## Security Solution sub teams - security-onboarding-and-lifecycle-mgt /x-pack/plugins/security_solution/public/management/ @elastic/security-onboarding-and-lifecycle-mgt /x-pack/plugins/security_solution/public/common/lib/endpoint*/ @elastic/security-onboarding-and-lifecycle-mgt /x-pack/plugins/security_solution/public/common/components/endpoint/ @elastic/security-onboarding-and-lifecycle-mgt diff --git a/.i18nrc.json b/.i18nrc.json index 2709d5ad7a671..68bb343092f25 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -37,6 +37,7 @@ "presentationUtil": "src/plugins/presentation_util", "indexPatternFieldEditor": "src/plugins/index_pattern_field_editor", "indexPatternManagement": "src/plugins/index_pattern_management", + "interactiveSetup": "src/plugins/interactive_setup", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", "kibanaOverview": "src/plugins/kibana_overview", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index d50c58c7df9bb..eaaab3386a795 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -136,6 +136,10 @@ for use in their own application. in Kibana, e.g. visualizations. It has the form of a flyout panel. +|{kib-repo}blob/{branch}/src/plugins/interactive_setup/README.md[interactiveSetup] +|The plugin provides UI and APIs for the interactive setup mode. + + |{kib-repo}blob/{branch}/src/plugins/kibana_legacy/README.md[kibanaLegacy] |This plugin contains several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. @@ -276,10 +280,6 @@ In general this plugin provides: |The Usage Collection Service defines a set of APIs for other plugins to report the usage of their features. At the same time, it provides necessary the APIs for other services (i.e.: telemetry, monitoring, ...) to consume that usage data. -|{kib-repo}blob/{branch}/src/plugins/user_setup/README.md[userSetup] -|The plugin provides UI and APIs for the interactive setup mode. - - |{kib-repo}blob/{branch}/src/plugins/vis_default_editor/README.md[visDefaultEditor] |The default editor is used in most primary visualizations, e.x. Area, Data table, Pie, etc. It acts as a container for a particular visualization and options tabs. Contains the default "Data" tab in public/components/sidebar/data_tab.tsx. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 5a0b376c4cbc5..d8a964decb87b 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -117,4 +117,4 @@ pageLoadAssetSize: expressionImage: 19288 expressionMetric: 22238 expressionShape: 30033 - userSetup: 18532 + interactiveSetup: 18532 diff --git a/src/plugins/user_setup/README.md b/src/plugins/interactive_setup/README.md similarity index 69% rename from src/plugins/user_setup/README.md rename to src/plugins/interactive_setup/README.md index 61ec964f5bb80..9fd43eb0445b6 100644 --- a/src/plugins/user_setup/README.md +++ b/src/plugins/interactive_setup/README.md @@ -1,3 +1,3 @@ -# `userSetup` plugin +# `interactiveSetup` plugin The plugin provides UI and APIs for the interactive setup mode. diff --git a/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts b/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts new file mode 100644 index 0000000000000..4e1506f69990c --- /dev/null +++ b/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Describes current status of the Elasticsearch connection. + */ +export enum ElasticsearchConnectionStatus { + /** + * Indicates that Kibana hasn't figured out yet if existing Elasticsearch connection configuration is valid. + */ + Unknown = 'unknown', + + /** + * Indicates that current Elasticsearch connection configuration valid and sufficient. + */ + Configured = 'configured', + + /** + * Indicates that current Elasticsearch connection configuration isn't valid or not sufficient. + */ + NotConfigured = 'not-configured', +} diff --git a/src/plugins/user_setup/server/plugin.ts b/src/plugins/interactive_setup/common/index.ts similarity index 62% rename from src/plugins/user_setup/server/plugin.ts rename to src/plugins/interactive_setup/common/index.ts index 918c9a2007935..f736d1e230122 100644 --- a/src/plugins/user_setup/server/plugin.ts +++ b/src/plugins/interactive_setup/common/index.ts @@ -6,12 +6,5 @@ * Side Public License, v 1. */ -import type { CoreSetup, CoreStart, Plugin } from 'src/core/server'; - -export class UserSetupPlugin implements Plugin { - public setup(core: CoreSetup) {} - - public start(core: CoreStart) {} - - public stop() {} -} +export type { InteractiveSetupViewState, EnrollmentToken } from './types'; +export { ElasticsearchConnectionStatus } from './elasticsearch_connection_status'; diff --git a/src/plugins/interactive_setup/common/types.ts b/src/plugins/interactive_setup/common/types.ts new file mode 100644 index 0000000000000..4df7c8eaa9724 --- /dev/null +++ b/src/plugins/interactive_setup/common/types.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ElasticsearchConnectionStatus } from './elasticsearch_connection_status'; + +/** + * A set of state details that interactive setup view retrieves from the Kibana server. + */ +export interface InteractiveSetupViewState { + /** + * Current status of the Elasticsearch connection. + */ + elasticsearchConnectionStatus: ElasticsearchConnectionStatus; +} + +/** + * The token that allows one to configure Kibana instance to communicate with an existing Elasticsearch cluster that + * has security features enabled. + */ +export interface EnrollmentToken { + /** + * The version of the Elasticsearch node that generated this enrollment token. + */ + ver: string; + + /** + * An array of addresses in the form of `:` or `:` where the Elasticsearch node is listening for HTTP connections. + */ + adr: readonly string[]; + + /** + * The SHA-256 fingerprint of the CA certificate that is used to sign the certificate that the Elasticsearch node presents for HTTP over TLS connections. + */ + fgr: string; + + /** + * An Elasticsearch API key (not encoded) that can be used as credentials authorized to call the enrollment related APIs in Elasticsearch. + */ + key: string; +} diff --git a/src/plugins/user_setup/jest.config.js b/src/plugins/interactive_setup/jest.config.js similarity index 88% rename from src/plugins/user_setup/jest.config.js rename to src/plugins/interactive_setup/jest.config.js index 75e355e230c5d..e9f1f479d66aa 100644 --- a/src/plugins/user_setup/jest.config.js +++ b/src/plugins/interactive_setup/jest.config.js @@ -9,5 +9,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/src/plugins/user_setup'], + roots: ['/src/plugins/interactive_setup'], }; diff --git a/src/plugins/user_setup/kibana.json b/src/plugins/interactive_setup/kibana.json similarity index 74% rename from src/plugins/user_setup/kibana.json rename to src/plugins/interactive_setup/kibana.json index 192fd42cd3e26..10270bf5b2c03 100644 --- a/src/plugins/user_setup/kibana.json +++ b/src/plugins/interactive_setup/kibana.json @@ -1,5 +1,5 @@ { - "id": "userSetup", + "id": "interactiveSetup", "owner": { "name": "Platform Security", "githubTeam": "kibana-security" @@ -7,7 +7,8 @@ "description": "This plugin provides UI and APIs for the interactive setup mode.", "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": ["userSetup"], + "type": "preboot", + "configPath": ["interactiveSetup"], "server": true, "ui": true } diff --git a/src/plugins/user_setup/public/app.tsx b/src/plugins/interactive_setup/public/app.tsx similarity index 100% rename from src/plugins/user_setup/public/app.tsx rename to src/plugins/interactive_setup/public/app.tsx diff --git a/src/plugins/user_setup/public/index.ts b/src/plugins/interactive_setup/public/index.ts similarity index 100% rename from src/plugins/user_setup/public/index.ts rename to src/plugins/interactive_setup/public/index.ts diff --git a/src/plugins/user_setup/public/plugin.tsx b/src/plugins/interactive_setup/public/plugin.tsx similarity index 92% rename from src/plugins/user_setup/public/plugin.tsx rename to src/plugins/interactive_setup/public/plugin.tsx index 677c27cc456dc..375f04e5047d5 100644 --- a/src/plugins/user_setup/public/plugin.tsx +++ b/src/plugins/interactive_setup/public/plugin.tsx @@ -10,13 +10,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; import type { CoreSetup, CoreStart, Plugin } from 'src/core/public'; + import { App } from './app'; export class UserSetupPlugin implements Plugin { public setup(core: CoreSetup) { core.application.register({ - id: 'userSetup', - title: 'User Setup', + id: 'interactiveSetup', + title: 'Interactive Setup', chromeless: true, mount: (params) => { ReactDOM.render(, params.element); diff --git a/src/plugins/user_setup/server/config.ts b/src/plugins/interactive_setup/server/config.ts similarity index 100% rename from src/plugins/user_setup/server/config.ts rename to src/plugins/interactive_setup/server/config.ts diff --git a/src/plugins/user_setup/server/index.ts b/src/plugins/interactive_setup/server/index.ts similarity index 69% rename from src/plugins/user_setup/server/index.ts rename to src/plugins/interactive_setup/server/index.ts index 2a43cbbf65c9d..018c6875b3c04 100644 --- a/src/plugins/user_setup/server/index.ts +++ b/src/plugins/interactive_setup/server/index.ts @@ -7,7 +7,11 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import type { PluginConfigDescriptor } from 'src/core/server'; +import type { + PluginConfigDescriptor, + PluginInitializer, + PluginInitializerContext, +} from 'src/core/server'; import { ConfigSchema } from './config'; import { UserSetupPlugin } from './plugin'; @@ -16,4 +20,6 @@ export const config: PluginConfigDescriptor> = { schema: ConfigSchema, }; -export const plugin = () => new UserSetupPlugin(); +export const plugin: PluginInitializer = ( + initializerContext: PluginInitializerContext +) => new UserSetupPlugin(initializerContext); diff --git a/src/plugins/interactive_setup/server/plugin.ts b/src/plugins/interactive_setup/server/plugin.ts new file mode 100644 index 0000000000000..6b2a12bad76bc --- /dev/null +++ b/src/plugins/interactive_setup/server/plugin.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Subscription } from 'rxjs'; + +import type { TypeOf } from '@kbn/config-schema'; +import type { CorePreboot, Logger, PluginInitializerContext, PrebootPlugin } from 'src/core/server'; + +import { ElasticsearchConnectionStatus } from '../common'; +import type { ConfigSchema, ConfigType } from './config'; +import { defineRoutes } from './routes'; + +export class UserSetupPlugin implements PrebootPlugin { + readonly #logger: Logger; + + #configSubscription?: Subscription; + #config?: ConfigType; + readonly #getConfig = () => { + if (!this.#config) { + throw new Error('Config is not available.'); + } + return this.#config; + }; + + #elasticsearchConnectionStatus = ElasticsearchConnectionStatus.Unknown; + readonly #getElasticsearchConnectionStatus = () => { + return this.#elasticsearchConnectionStatus; + }; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.#logger = this.initializerContext.logger.get(); + } + + public setup(core: CorePreboot) { + this.#configSubscription = this.initializerContext.config + .create>() + .subscribe((config) => { + this.#config = config; + }); + + // We shouldn't activate interactive setup mode if we detect that user has already configured + // Elasticsearch connection manually: either if Kibana system user credentials are specified or + // user specified non-default host for the Elasticsearch. + const shouldActiveSetupMode = + !core.elasticsearch.config.credentialsSpecified && + core.elasticsearch.config.hosts.length === 1 && + core.elasticsearch.config.hosts[0] === 'http://localhost:9200'; + if (!shouldActiveSetupMode) { + this.#logger.debug( + 'Interactive setup mode will not be activated since Elasticsearch connection is already configured.' + ); + return; + } + + let completeSetup: (result: { shouldReloadConfig: boolean }) => void; + core.preboot.holdSetupUntilResolved( + 'Validating Elasticsearch connection configuration…', + new Promise((resolve) => { + completeSetup = resolve; + }) + ); + + // If preliminary check above indicates that user didn't alter default Elasticsearch connection + // details, it doesn't mean Elasticsearch connection isn't configured. There is a chance that they + // already disabled security features in Elasticsearch and everything should work by default. + // We should check if we can connect to Elasticsearch with default configuration to know if we + // need to activate interactive setup. This check can take some time, so we should register our + // routes to let interactive setup UI to handle user requests until the check is complete. + core.elasticsearch + .createClient('ping') + .asInternalUser.ping() + .then( + (pingResponse) => { + if (pingResponse.body) { + this.#logger.debug( + 'Kibana is already properly configured to connect to Elasticsearch. Interactive setup mode will not be activated.' + ); + this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.Configured; + completeSetup({ shouldReloadConfig: false }); + } else { + this.#logger.debug( + 'Kibana is not properly configured to connect to Elasticsearch. Interactive setup mode will be activated.' + ); + this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.NotConfigured; + } + }, + () => { + // TODO: we should probably react differently to different errors. 401 - credentials aren't correct, etc. + // Do we want to constantly ping ES if interactive mode UI isn't active? Just in case user runs Kibana and then + // configure Elasticsearch so that it can eventually connect to it without any configuration changes? + this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.NotConfigured; + } + ); + + core.http.registerRoutes('', (router) => { + defineRoutes({ + router, + basePath: core.http.basePath, + logger: this.#logger.get('routes'), + getConfig: this.#getConfig.bind(this), + getElasticsearchConnectionStatus: this.#getElasticsearchConnectionStatus.bind(this), + }); + }); + } + + public stop() { + this.#logger.debug('Stopping plugin'); + + if (this.#configSubscription) { + this.#configSubscription.unsubscribe(); + this.#configSubscription = undefined; + } + } +} diff --git a/src/plugins/interactive_setup/server/routes/enroll.ts b/src/plugins/interactive_setup/server/routes/enroll.ts new file mode 100644 index 0000000000000..a600d18109760 --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/enroll.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from './'; + +/** + * Defines routes to deal with Elasticsearch `enroll_kibana` APIs. + */ +export function defineEnrollRoutes({ router }: RouteDefinitionParams) { + router.post( + { + path: '/internal/interactive_setup/enroll', + validate: { + body: schema.object({ token: schema.string() }), + }, + options: { authRequired: false }, + }, + async (context, request, response) => { + return response.forbidden({ + body: { message: `API is not implemented yet.` }, + }); + } + ); +} diff --git a/src/plugins/interactive_setup/server/routes/index.ts b/src/plugins/interactive_setup/server/routes/index.ts new file mode 100644 index 0000000000000..0f14f5ffac8ec --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/index.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IBasePath, IRouter, Logger } from 'src/core/server'; + +import type { ElasticsearchConnectionStatus } from '../../common'; +import type { ConfigType } from '../config'; +import { defineEnrollRoutes } from './enroll'; + +/** + * Describes parameters used to define HTTP routes. + */ +export interface RouteDefinitionParams { + readonly router: IRouter; + readonly basePath: IBasePath; + readonly logger: Logger; + readonly getConfig: () => ConfigType; + readonly getElasticsearchConnectionStatus: () => ElasticsearchConnectionStatus; +} + +export function defineRoutes(params: RouteDefinitionParams) { + defineEnrollRoutes(params); +} diff --git a/src/plugins/user_setup/tsconfig.json b/src/plugins/interactive_setup/tsconfig.json similarity index 81% rename from src/plugins/user_setup/tsconfig.json rename to src/plugins/interactive_setup/tsconfig.json index d211a70f12df3..530e01a034b00 100644 --- a/src/plugins/user_setup/tsconfig.json +++ b/src/plugins/interactive_setup/tsconfig.json @@ -7,6 +7,6 @@ "declaration": true, "declarationMap": true }, - "include": ["public/**/*", "server/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*"], "references": [{ "path": "../../core/tsconfig.json" }] } diff --git a/tsconfig.json b/tsconfig.json index 082325306e379..ab5a02702e611 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,6 +31,7 @@ { "path": "./src/plugins/expressions/tsconfig.json" }, { "path": "./src/plugins/home/tsconfig.json" }, { "path": "./src/plugins/inspector/tsconfig.json" }, + { "path": "./src/plugins/interactive_setup/tsconfig.json" }, { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, { "path": "./src/plugins/kibana_overview/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index bbaf18d29ae2a..1def88087ed86 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -16,6 +16,7 @@ { "path": "./src/plugins/expressions/tsconfig.json" }, { "path": "./src/plugins/home/tsconfig.json" }, { "path": "./src/plugins/inspector/tsconfig.json" }, + { "path": "./src/plugins/interactive_setup/tsconfig.json" }, { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, { "path": "./src/plugins/kibana_overview/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" },