Implement interactiveSetup plugin server side functionality: setup layout (#105222)

This commit is contained in:
Aleh Zasypkin 2021-08-03 15:52:11 +02:00 committed by GitHub
parent ff2a5a8566
commit ed28155165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 280 additions and 25 deletions

View file

@ -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}',

4
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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",

View file

@ -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.

View file

@ -117,4 +117,4 @@ pageLoadAssetSize:
expressionImage: 19288
expressionMetric: 22238
expressionShape: 30033
userSetup: 18532
interactiveSetup: 18532

View file

@ -1,3 +1,3 @@
# `userSetup` plugin
# `interactiveSetup` plugin
The plugin provides UI and APIs for the interactive setup mode.

View file

@ -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',
}

View file

@ -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';

View file

@ -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 `<hostname>:<port>` or `<ip_address>:<port>` 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;
}

View file

@ -9,5 +9,5 @@
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/user_setup'],
roots: ['<rootDir>/src/plugins/interactive_setup'],
};

View file

@ -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
}

View file

@ -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(<App />, params.element);

View file

@ -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<TypeOf<typeof ConfigSchema>> = {
schema: ConfigSchema,
};
export const plugin = () => new UserSetupPlugin();
export const plugin: PluginInitializer<void, never> = (
initializerContext: PluginInitializerContext
) => new UserSetupPlugin(initializerContext);

View file

@ -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<TypeOf<typeof ConfigSchema>>()
.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;
}
}
}

View file

@ -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.` },
});
}
);
}

View file

@ -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);
}

View file

@ -7,6 +7,6 @@
"declaration": true,
"declarationMap": true
},
"include": ["public/**/*", "server/**/*"],
"include": ["common/**/*", "public/**/*", "server/**/*"],
"references": [{ "path": "../../core/tsconfig.json" }]
}

View file

@ -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" },

View file

@ -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" },