mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add interactive setup CLI (#114493)
* Add interactive setup CLI
* Added tsconfig
* ignore all CLI dev.js files when building
* add cli_init to the root TS project and setup necessary ref
* Fix type errors
* Added suggestions from code review
* ts fix
* fixed build dependencies
* Added suggestions from code review
* fix type definitions
* fix types
* upgraded commander to fix ts issues
* Revert "upgraded commander to fix ts issues"
This reverts commit 52b8943222
.
* upgraded commander
Co-authored-by: spalger <spalger@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
abd5e9ffa8
commit
b879a9a497
18 changed files with 442 additions and 30 deletions
|
@ -197,7 +197,7 @@
|
|||
"chroma-js": "^1.4.1",
|
||||
"classnames": "2.2.6",
|
||||
"color": "1.0.3",
|
||||
"commander": "^3.0.2",
|
||||
"commander": "^4.1.1",
|
||||
"compare-versions": "3.5.1",
|
||||
"concat-stream": "1.6.2",
|
||||
"constate": "^1.3.2",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"idx": "^2.5.6",
|
||||
"immer": "^9.0.6",
|
||||
"inline-style": "^2.0.0",
|
||||
"inquirer": "^7.3.3",
|
||||
"intl": "^1.2.5",
|
||||
"intl-format-cache": "^2.1.0",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
|
@ -297,6 +298,7 @@
|
|||
"object-hash": "^1.3.1",
|
||||
"object-path-immutable": "^3.1.1",
|
||||
"opn": "^5.5.0",
|
||||
"ora": "^4.0.4",
|
||||
"p-limit": "^3.0.1",
|
||||
"p-map": "^4.0.0",
|
||||
"p-retry": "^4.2.0",
|
||||
|
@ -720,7 +722,6 @@
|
|||
"html": "1.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"http-proxy": "^1.18.1",
|
||||
"inquirer": "^7.3.3",
|
||||
"is-glob": "^4.0.1",
|
||||
"is-path-inside": "^3.0.2",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
|
@ -762,7 +763,6 @@
|
|||
"null-loader": "^3.0.0",
|
||||
"nyc": "^15.0.1",
|
||||
"oboe": "^2.1.4",
|
||||
"ora": "^4.0.4",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"pbf": "3.2.1",
|
||||
"pirates": "^4.0.1",
|
||||
|
|
9
scripts/kibana_setup.js
Normal file
9
scripts/kibana_setup.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('../src/cli_setup/dev');
|
20
src/cli_plugin/lib/logger.d.ts
vendored
Normal file
20
src/cli_plugin/lib/logger.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface LoggerOptions {
|
||||
silent?: boolean;
|
||||
quiet?: boolean;
|
||||
}
|
||||
|
||||
export declare class Logger {
|
||||
constructor(settings?: LoggerOptions);
|
||||
|
||||
log(data: string, sameLine?: boolean): void;
|
||||
|
||||
error(data: string): void;
|
||||
}
|
118
src/cli_setup/cli_setup.ts
Normal file
118
src/cli_setup/cli_setup.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 { kibanaPackageJson } from '@kbn/utils';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { Command } from 'commander';
|
||||
import { getConfigPath } from '@kbn/utils';
|
||||
|
||||
import {
|
||||
ElasticsearchService,
|
||||
EnrollResult,
|
||||
} from '../plugins/interactive_setup/server/elasticsearch_service';
|
||||
import { getDetailedErrorMessage } from '../plugins/interactive_setup/server/errors';
|
||||
import {
|
||||
promptToken,
|
||||
getCommand,
|
||||
decodeEnrollmentToken,
|
||||
kibanaConfigWriter,
|
||||
elasticsearch,
|
||||
} from './utils';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
|
||||
const program = new Command('bin/kibana-setup');
|
||||
|
||||
program
|
||||
.version(kibanaPackageJson.version)
|
||||
.description(
|
||||
'This command walks you through all required steps to securely connect Kibana with Elasticsearch'
|
||||
)
|
||||
.option('-t, --token <token>', 'Elasticsearch enrollment token')
|
||||
.option('-s, --silent', 'Prevent all logging');
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
interface SetupOptions {
|
||||
token?: string;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
const options = program.opts() as SetupOptions;
|
||||
const spinner = ora();
|
||||
const logger = new Logger(options);
|
||||
|
||||
async function initCommand() {
|
||||
const token = decodeEnrollmentToken(
|
||||
options.token ?? (options.silent ? undefined : await promptToken())
|
||||
);
|
||||
if (!token) {
|
||||
logger.error(chalk.red('Invalid enrollment token provided.'));
|
||||
logger.error('');
|
||||
logger.error('To generate a new enrollment token run:');
|
||||
logger.error(` ${getCommand('elasticsearch-create-enrollment-token', '-s kibana')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!(await kibanaConfigWriter.isConfigWritable())) {
|
||||
logger.error(chalk.red('Kibana does not have enough permissions to write to the config file.'));
|
||||
logger.error('');
|
||||
logger.error('To grant write access run:');
|
||||
logger.error(` chmod +w ${getConfigPath()}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
logger.log('');
|
||||
if (!options.silent) {
|
||||
spinner.start(chalk.dim('Configuring Kibana...'));
|
||||
}
|
||||
|
||||
let configToWrite: EnrollResult;
|
||||
try {
|
||||
configToWrite = await elasticsearch.enroll({
|
||||
hosts: token.adr,
|
||||
apiKey: token.key,
|
||||
caFingerprint: ElasticsearchService.formatFingerprint(token.fgr),
|
||||
});
|
||||
} catch (error) {
|
||||
if (!options.silent) {
|
||||
spinner.fail(
|
||||
`${chalk.bold('Unable to enroll with Elasticsearch:')} ${chalk.red(
|
||||
`${getDetailedErrorMessage(error)}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
logger.error('');
|
||||
logger.error('To generate a new enrollment token run:');
|
||||
logger.error(` ${getCommand('elasticsearch-create-enrollment-token', '-s kibana')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
await kibanaConfigWriter.writeConfig(configToWrite);
|
||||
} catch (error) {
|
||||
if (!options.silent) {
|
||||
spinner.fail(
|
||||
`${chalk.bold('Unable to configure Kibana:')} ${chalk.red(
|
||||
`${getDetailedErrorMessage(error)}`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
logger.error(chalk.red(`${getDetailedErrorMessage(error)}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.silent) {
|
||||
spinner.succeed(chalk.bold('Kibana configured successfully.'));
|
||||
}
|
||||
logger.log('');
|
||||
logger.log('To start Kibana run:');
|
||||
logger.log(` ${getCommand('kibana')}`);
|
||||
}
|
||||
|
||||
initCommand();
|
10
src/cli_setup/dev.js
Normal file
10
src/cli_setup/dev.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('../setup_node_env');
|
||||
require('./cli_setup');
|
10
src/cli_setup/dist.js
Normal file
10
src/cli_setup/dist.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('../setup_node_env/dist');
|
||||
require('./cli_setup');
|
13
src/cli_setup/jest.config.js
Normal file
13
src/cli_setup/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/src/cli_setup'],
|
||||
};
|
76
src/cli_setup/utils.test.ts
Normal file
76
src/cli_setup/utils.test.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { decodeEnrollmentToken, getCommand } from './utils';
|
||||
import type { EnrollmentToken } from '../plugins/interactive_setup/common';
|
||||
|
||||
describe('kibana setup cli', () => {
|
||||
describe('getCommand', () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
it('should format windows correctly', () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'win32',
|
||||
});
|
||||
expect(getCommand('kibana')).toEqual('bin\\kibana.bat');
|
||||
expect(getCommand('kibana', '--silent')).toEqual('bin\\kibana.bat --silent');
|
||||
});
|
||||
|
||||
it('should format unix correctly', () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'linux',
|
||||
});
|
||||
expect(getCommand('kibana')).toEqual('bin/kibana');
|
||||
expect(getCommand('kibana', '--silent')).toEqual('bin/kibana --silent');
|
||||
});
|
||||
|
||||
afterAll(function () {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: originalPlatform,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeEnrollmentToken', () => {
|
||||
const token: EnrollmentToken = {
|
||||
ver: '8.0.0',
|
||||
adr: ['localhost:9200'],
|
||||
fgr: 'AA:C8:2C:2E:09:58:F4:FE:A1:D2:AB:7F:13:70:C2:7D:EB:FD:A2:23:88:13:E4:DA:3A:D0:59:D0:09:00:07:36',
|
||||
key: 'JH-36HoBo4EYIoVhHh2F:uEo4dksARMq_BSHaAHUr8Q',
|
||||
};
|
||||
|
||||
it('should decode a valid token', () => {
|
||||
expect(decodeEnrollmentToken(btoa(JSON.stringify(token)))).toEqual({
|
||||
adr: ['https://localhost:9200'],
|
||||
fgr: 'AA:C8:2C:2E:09:58:F4:FE:A1:D2:AB:7F:13:70:C2:7D:EB:FD:A2:23:88:13:E4:DA:3A:D0:59:D0:09:00:07:36',
|
||||
key: 'SkgtMzZIb0JvNEVZSW9WaEhoMkY6dUVvNGRrc0FSTXFfQlNIYUFIVXI4UQ==',
|
||||
ver: '8.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not decode an invalid token', () => {
|
||||
expect(decodeEnrollmentToken(JSON.stringify(token))).toBeUndefined();
|
||||
expect(
|
||||
decodeEnrollmentToken(
|
||||
btoa(
|
||||
JSON.stringify({
|
||||
ver: [''],
|
||||
adr: null,
|
||||
fgr: false,
|
||||
key: undefined,
|
||||
})
|
||||
)
|
||||
)
|
||||
).toBeUndefined();
|
||||
expect(decodeEnrollmentToken(btoa(JSON.stringify({})))).toBeUndefined();
|
||||
expect(decodeEnrollmentToken(btoa(JSON.stringify([])))).toBeUndefined();
|
||||
expect(decodeEnrollmentToken(btoa(JSON.stringify(null)))).toBeUndefined();
|
||||
expect(decodeEnrollmentToken(btoa(JSON.stringify('')))).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
91
src/cli_setup/utils.ts
Normal file
91
src/cli_setup/utils.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { getConfigPath } from '@kbn/utils';
|
||||
import inquirer from 'inquirer';
|
||||
import { duration } from 'moment';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
import { Logger } from '../core/server';
|
||||
import { ClusterClient } from '../core/server/elasticsearch/client';
|
||||
import { configSchema } from '../core/server/elasticsearch';
|
||||
import { ElasticsearchService } from '../plugins/interactive_setup/server/elasticsearch_service';
|
||||
import { KibanaConfigWriter } from '../plugins/interactive_setup/server/kibana_config_writer';
|
||||
import type { EnrollmentToken } from '../plugins/interactive_setup/common';
|
||||
|
||||
const noop = () => {};
|
||||
const logger: Logger = {
|
||||
debug: noop,
|
||||
error: noop,
|
||||
warn: noop,
|
||||
trace: noop,
|
||||
info: noop,
|
||||
fatal: noop,
|
||||
log: noop,
|
||||
get: () => logger,
|
||||
};
|
||||
|
||||
export const kibanaConfigWriter = new KibanaConfigWriter(getConfigPath(), logger);
|
||||
export const elasticsearch = new ElasticsearchService(logger).setup({
|
||||
connectionCheckInterval: duration(Infinity),
|
||||
elasticsearch: {
|
||||
createClient: (type, config) => {
|
||||
const defaults = configSchema.validate({});
|
||||
return new ClusterClient(
|
||||
merge(
|
||||
defaults,
|
||||
{
|
||||
hosts: Array.isArray(defaults.hosts) ? defaults.hosts : [defaults.hosts],
|
||||
},
|
||||
config
|
||||
),
|
||||
logger,
|
||||
type
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export async function promptToken() {
|
||||
const answers = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'token',
|
||||
message: 'Enter enrollment token:',
|
||||
validate: (value = '') => (decodeEnrollmentToken(value) ? true : 'Invalid enrollment token'),
|
||||
});
|
||||
return answers.token;
|
||||
}
|
||||
|
||||
export function decodeEnrollmentToken(enrollmentToken: string): EnrollmentToken | undefined {
|
||||
try {
|
||||
const json = JSON.parse(atob(enrollmentToken)) as EnrollmentToken;
|
||||
if (
|
||||
!Array.isArray(json.adr) ||
|
||||
json.adr.some((adr) => typeof adr !== 'string') ||
|
||||
typeof json.fgr !== 'string' ||
|
||||
typeof json.key !== 'string' ||
|
||||
typeof json.ver !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return { ...json, adr: json.adr.map((adr) => `https://${adr}`), key: btoa(json.key) };
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
function btoa(str: string) {
|
||||
return Buffer.from(str, 'binary').toString('base64');
|
||||
}
|
||||
|
||||
function atob(str: string) {
|
||||
return Buffer.from(str, 'base64').toString('binary');
|
||||
}
|
||||
|
||||
export function getCommand(command: string, args?: string) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
return `${isWindows ? `bin\\${command}.bat` : `bin/${command}`}${args ? ` ${args}` : ''}`;
|
||||
}
|
29
src/dev/build/tasks/bin/scripts/kibana-setup
Executable file
29
src/dev/build/tasks/bin/scripts/kibana-setup
Executable file
|
@ -0,0 +1,29 @@
|
|||
#!/bin/sh
|
||||
SCRIPT=$0
|
||||
|
||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=$(ls -ld "$SCRIPT")
|
||||
# Drop everything prior to ->
|
||||
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=$(dirname "$SCRIPT")/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
DIR="$(dirname "${SCRIPT}")/.."
|
||||
CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"}
|
||||
NODE="${DIR}/node/bin/node"
|
||||
test -x "$NODE"
|
||||
if [ ! -x "$NODE" ]; then
|
||||
echo "unable to find usable node.js executable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "${CONFIG_DIR}/node.options" ]; then
|
||||
KBN_NODE_OPTS="$(grep -v ^# < ${CONFIG_DIR}/node.options | xargs)"
|
||||
fi
|
||||
|
||||
NODE_OPTIONS="$KBN_NODE_OPTS $NODE_OPTIONS" "${NODE}" "${DIR}/src/cli_setup/dist" "$@"
|
35
src/dev/build/tasks/bin/scripts/kibana-setup.bat
Executable file
35
src/dev/build/tasks/bin/scripts/kibana-setup.bat
Executable file
|
@ -0,0 +1,35 @@
|
|||
@echo off
|
||||
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
set SCRIPT_DIR=%~dp0
|
||||
for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI
|
||||
|
||||
set NODE=%DIR%\node\node.exe
|
||||
|
||||
If Not Exist "%NODE%" (
|
||||
Echo unable to find usable node.js executable.
|
||||
Exit /B 1
|
||||
)
|
||||
|
||||
set CONFIG_DIR=%KBN_PATH_CONF%
|
||||
If ["%KBN_PATH_CONF%"] == [] (
|
||||
set "CONFIG_DIR=%DIR%\config"
|
||||
)
|
||||
|
||||
IF EXIST "%CONFIG_DIR%\node.options" (
|
||||
for /F "usebackq eol=# tokens=*" %%i in ("%CONFIG_DIR%\node.options") do (
|
||||
If [!NODE_OPTIONS!] == [] (
|
||||
set "NODE_OPTIONS=%%i"
|
||||
) Else (
|
||||
set "NODE_OPTIONS=!NODE_OPTIONS! %%i"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
TITLE Kibana Setup
|
||||
"%NODE%" "%DIR%\src\cli_setup\dist" %*
|
||||
|
||||
:finally
|
||||
|
||||
ENDLOCAL
|
|
@ -26,7 +26,7 @@ export const CopySource: Task = {
|
|||
'!src/test_utils/**',
|
||||
'!src/fixtures/**',
|
||||
'!src/cli/repl/**',
|
||||
'!src/cli/dev.js',
|
||||
'!src/cli*/dev.js',
|
||||
'!src/functional_test_runner/**',
|
||||
'!src/dev/**',
|
||||
'!**/jest.config.js',
|
||||
|
|
|
@ -22,11 +22,7 @@ async function getDependencies(cwd: string, entries: string[]) {
|
|||
export async function findUsedDependencies(listedPkgDependencies: any, baseDir: any) {
|
||||
// Define the entry points for the server code in order to
|
||||
// start here later looking for the server side dependencies
|
||||
const mainCodeEntries = [
|
||||
Path.resolve(baseDir, `src/cli/dist.js`),
|
||||
Path.resolve(baseDir, `src/cli_keystore/dist.js`),
|
||||
Path.resolve(baseDir, `src/cli_plugin/dist.js`),
|
||||
];
|
||||
const mainCodeEntries = await globby(normalize(Path.resolve(baseDir, `src/cli*/dist.js`)));
|
||||
|
||||
const discoveredPluginEntries = await globby([
|
||||
normalize(Path.resolve(baseDir, `src/plugins/**/server/index.js`)),
|
||||
|
|
|
@ -193,7 +193,7 @@ const EnrollmentTokenDetails: FunctionComponent<EnrollmentTokenDetailsProps> = (
|
|||
</EuiText>
|
||||
);
|
||||
|
||||
export function decodeEnrollmentToken(enrollmentToken: string) {
|
||||
export function decodeEnrollmentToken(enrollmentToken: string): EnrollmentToken | undefined {
|
||||
try {
|
||||
const json = JSON.parse(atob(enrollmentToken)) as EnrollmentToken;
|
||||
if (
|
||||
|
|
|
@ -34,7 +34,7 @@ import { getDetailedErrorMessage, getErrorStatusCode } from './errors';
|
|||
|
||||
export interface EnrollParameters {
|
||||
apiKey: string;
|
||||
hosts: string[];
|
||||
hosts: readonly string[];
|
||||
caFingerprint: string;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ export interface ElasticsearchServiceSetupDeps {
|
|||
/**
|
||||
* Core Elasticsearch service preboot contract;
|
||||
*/
|
||||
elasticsearch: ElasticsearchServicePreboot;
|
||||
elasticsearch: Pick<ElasticsearchServicePreboot, 'createClient'>;
|
||||
|
||||
/**
|
||||
* Interval for the Elasticsearch connection check (whether it's configured or not).
|
||||
|
@ -169,7 +169,7 @@ export class ElasticsearchService {
|
|||
* the Elasticsearch node we're enrolling with. Should be in a form of a hex colon-delimited string in upper case.
|
||||
*/
|
||||
private async enroll(
|
||||
elasticsearch: ElasticsearchServicePreboot,
|
||||
elasticsearch: Pick<ElasticsearchServicePreboot, 'createClient'>,
|
||||
{ apiKey, hosts, caFingerprint }: EnrollParameters
|
||||
) {
|
||||
const scopeableRequest: ScopeableRequest = { headers: { authorization: `ApiKey ${apiKey}` } };
|
||||
|
@ -257,7 +257,7 @@ export class ElasticsearchService {
|
|||
}
|
||||
|
||||
private async authenticate(
|
||||
elasticsearch: ElasticsearchServicePreboot,
|
||||
elasticsearch: Pick<ElasticsearchServicePreboot, 'createClient'>,
|
||||
{ host, username, password, caCert }: AuthenticateParameters
|
||||
) {
|
||||
const client = elasticsearch.createClient('authenticate', {
|
||||
|
@ -281,7 +281,10 @@ export class ElasticsearchService {
|
|||
}
|
||||
}
|
||||
|
||||
private async ping(elasticsearch: ElasticsearchServicePreboot, host: string) {
|
||||
private async ping(
|
||||
elasticsearch: Pick<ElasticsearchServicePreboot, 'createClient'>,
|
||||
host: string
|
||||
) {
|
||||
const client = elasticsearch.createClient('ping', {
|
||||
hosts: [host],
|
||||
username: '',
|
||||
|
@ -393,4 +396,15 @@ export class ElasticsearchService {
|
|||
.replace(/([^\n]{1,65})/g, '$1\n')
|
||||
.replace(/\n$/g, '')}\n-----END CERTIFICATE-----\n`;
|
||||
}
|
||||
|
||||
public static formatFingerprint(caFingerprint: string) {
|
||||
// Convert a plain hex string returned in the enrollment token to a format that ES client
|
||||
// expects, i.e. to a colon delimited hex string in upper case: deadbeef -> DE:AD:BE:EF.
|
||||
return (
|
||||
caFingerprint
|
||||
.toUpperCase()
|
||||
.match(/.{1,2}/g)
|
||||
?.join(':') ?? ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
ERROR_KIBANA_CONFIG_NOT_WRITABLE,
|
||||
ERROR_OUTSIDE_PREBOOT_STAGE,
|
||||
} from '../../common';
|
||||
import { ElasticsearchService } from '../elasticsearch_service';
|
||||
import type { EnrollResult } from '../elasticsearch_service';
|
||||
import type { WriteConfigParameters } from '../kibana_config_writer';
|
||||
import type { RouteDefinitionParams } from './';
|
||||
|
@ -92,20 +93,12 @@ export function defineEnrollRoutes({
|
|||
});
|
||||
}
|
||||
|
||||
// Convert a plain hex string returned in the enrollment token to a format that ES client
|
||||
// expects, i.e. to a colon delimited hex string in upper case: deadbeef -> DE:AD:BE:EF.
|
||||
const colonFormattedCaFingerprint =
|
||||
request.body.caFingerprint
|
||||
.toUpperCase()
|
||||
.match(/.{1,2}/g)
|
||||
?.join(':') ?? '';
|
||||
|
||||
let configToWrite: WriteConfigParameters & EnrollResult;
|
||||
try {
|
||||
configToWrite = await elasticsearch.enroll({
|
||||
apiKey: request.body.apiKey,
|
||||
hosts: request.body.hosts,
|
||||
caFingerprint: colonFormattedCaFingerprint,
|
||||
caFingerprint: ElasticsearchService.formatFingerprint(request.body.caFingerprint),
|
||||
});
|
||||
} catch {
|
||||
// For security reasons, we shouldn't leak to the user whether Elasticsearch node couldn't process enrollment
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"typings/**/*",
|
||||
|
||||
"src/cli/**/*",
|
||||
"src/cli_setup/**/*",
|
||||
"src/cli_plugin/**/*",
|
||||
"src/dev/**/*",
|
||||
"src/fixtures/**/*",
|
||||
|
||||
|
@ -17,6 +19,7 @@
|
|||
"references": [
|
||||
{ "path": "./src/core/tsconfig.json" },
|
||||
{ "path": "./src/plugins/usage_collection/tsconfig.json" },
|
||||
{ "path": "./src/plugins/interactive_setup/tsconfig.json" },
|
||||
{ "path": "./x-pack/plugins/reporting/tsconfig.json" },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10806,11 +10806,6 @@ commander@2.17.x, commander@~2.17.1:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
||||
|
||||
commander@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
|
||||
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
|
||||
|
||||
commander@^4.0.1, commander@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue