[8.8] Improve keystore CLI (#157359) (#157886)

# Backport

This will backport the following commits from `main` to `8.8`:
- [Improve keystore CLI
(#157359)](https://github.com/elastic/kibana/pull/157359)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Alex
Szabo","email":"alex.szabo@elastic.co"},"sourceCommit":{"committedDate":"2023-05-16T14:21:25Z","message":"Improve
keystore CLI (#157359)\n\n## Summary\r\n\r\nRelates to: #113217\r\n\r\n-
Add extra documentation to highlight behaviour of the kibana
keystore\r\n(for #113217)\r\n- Fix/Tidy-up commands (`create`, `list`)
where the extra unused\r\narguments were preventing the `options` from
being passed to the\r\nfunctions. Also remove unnecessary `async`
keyword from the `remove`\r\ncommand.\r\n- Added new `show`
command\r\n```\r\nUsage: bin/kibana-keystore show [options]
<key>\r\n\r\nDisplays the value of a single setting in the keystore.
Pass the -o (or --output) parameter to write the setting to a
file.\r\n\r\nOptions:\r\n -s, --silent prevent all logging\r\n -o,
--output <file> output value to a file\r\n -h, --help output usage
information\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Kaarina Tungseth
<kaarina.tungseth@elastic.co>","sha":"6ebfb8aa3ecd28ba1059e40ea8f9fea62ad52368","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Operations","release_note:skip","v8.8.0","v8.9.0"],"number":157359,"url":"https://github.com/elastic/kibana/pull/157359","mergeCommit":{"message":"Improve
keystore CLI (#157359)\n\n## Summary\r\n\r\nRelates to: #113217\r\n\r\n-
Add extra documentation to highlight behaviour of the kibana
keystore\r\n(for #113217)\r\n- Fix/Tidy-up commands (`create`, `list`)
where the extra unused\r\narguments were preventing the `options` from
being passed to the\r\nfunctions. Also remove unnecessary `async`
keyword from the `remove`\r\ncommand.\r\n- Added new `show`
command\r\n```\r\nUsage: bin/kibana-keystore show [options]
<key>\r\n\r\nDisplays the value of a single setting in the keystore.
Pass the -o (or --output) parameter to write the setting to a
file.\r\n\r\nOptions:\r\n -s, --silent prevent all logging\r\n -o,
--output <file> output value to a file\r\n -h, --help output usage
information\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Kaarina Tungseth
<kaarina.tungseth@elastic.co>","sha":"6ebfb8aa3ecd28ba1059e40ea8f9fea62ad52368"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/157359","number":157359,"mergeCommit":{"message":"Improve
keystore CLI (#157359)\n\n## Summary\r\n\r\nRelates to: #113217\r\n\r\n-
Add extra documentation to highlight behaviour of the kibana
keystore\r\n(for #113217)\r\n- Fix/Tidy-up commands (`create`, `list`)
where the extra unused\r\narguments were preventing the `options` from
being passed to the\r\nfunctions. Also remove unnecessary `async`
keyword from the `remove`\r\ncommand.\r\n- Added new `show`
command\r\n```\r\nUsage: bin/kibana-keystore show [options]
<key>\r\n\r\nDisplays the value of a single setting in the keystore.
Pass the -o (or --output) parameter to write the setting to a
file.\r\n\r\nOptions:\r\n -s, --silent prevent all logging\r\n -o,
--output <file> output value to a file\r\n -h, --help output usage
information\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Kaarina Tungseth
<kaarina.tungseth@elastic.co>","sha":"6ebfb8aa3ecd28ba1059e40ea8f9fea62ad52368"}}]}]
BACKPORT-->

Co-authored-by: Alex Szabo <alex.szabo@elastic.co>
This commit is contained in:
Kibana Machine 2023-05-16 11:38:03 -04:00 committed by GitHub
parent 0953d02ccc
commit e7e0a58578
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 188 additions and 5 deletions

View file

@ -5,7 +5,12 @@ Some settings are sensitive, and relying on filesystem permissions to protect
their values is not sufficient. For this use case, Kibana provides a
keystore, and the `kibana-keystore` tool to manage the settings in the keystore.
NOTE: All commands here should be run as the user which will run Kibana.
[NOTE]
====
* Run all commands as the user who runs {kib}.
* Only the settings with the `(Secure)` qualifier should be stored in the keystore.
Unsupported, extraneous or invalid JSON-string settings cause {kib} to fail to start up.
====
[float]
[[creating-keystore]]
@ -36,7 +41,8 @@ bin/kibana-keystore list
[[add-string-to-keystore]]
=== Add string settings
NOTE: Your input will be JSON-parsed to allow for object/array input configurations. To enforce string values, use "double quotes" around your input.
NOTE: Your input will be JSON-parsed to allow for object/array input configurations.
To enforce string values, use "double quotes" around your input.
Sensitive string settings, like authentication credentials for Elasticsearch
can be added using the `add` command:
@ -75,3 +81,14 @@ To remove a setting from the keystore, use the `remove` command:
----------------------------------------------------------------
bin/kibana-keystore remove the.setting.name.to.remove
----------------------------------------------------------------
[float]
[[read-settings]]
=== Read settings
To display the configured setting values, use the `show` command:
[source, sh]
----------------------------------------------------------------
bin/kibana-keystore show setting.key
----------------------------------------------------------------

View file

@ -17,6 +17,7 @@ import { createCli } from './create';
import { listCli } from './list';
import { addCli } from './add';
import { removeCli } from './remove';
import { showCli } from './show';
const argv = process.argv.slice();
const program = new Command('bin/kibana-keystore');
@ -31,6 +32,7 @@ createCli(program, keystore);
listCli(program, keystore);
addCli(program, keystore);
removeCli(program, keystore);
showCli(program, keystore);
program
.command('help <command>')

View file

@ -9,7 +9,7 @@
import { Logger } from '../cli/logger';
import { confirm } from './utils';
export async function create(keystore, command, options) {
export async function create(keystore, options) {
const logger = new Logger(options);
if (keystore.exists()) {

View file

@ -8,7 +8,7 @@
import { Logger } from '../cli/logger';
export function list(keystore, command, options = {}) {
export function list(keystore, options = {}) {
const logger = new Logger(options);
if (!keystore.exists()) {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
export async function remove(keystore, key) {
export function remove(keystore, key) {
keystore.remove(key);
keystore.save();
}

View file

@ -0,0 +1,101 @@
/*
* 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.
*/
/**
* This data blob has 3 key/values set:
* - foo: "turbo2000"
* - bar: {"sub": 0}
* - num: 12345
*/
const mockKeystoreData =
'1:ae/OomiywlzhXnR8DnGLHheyAklj4WcvDUOzeIyeQIHEmrY' +
'MIYOYHvduos7NDOgw3TFAuh7xs6z9i0juEo1zFeJeIr8yoyIxdGi1J8GUCO0/' +
'OeaKxvLjTjczwoxiy34kM6CzlnJhjwnALAMiBvbehMUaCVzxf3Fu/3Gk2qeux0OPhidJ4Pn/RPjdMA==';
jest.mock('fs', () => ({
readFileSync: jest.fn().mockImplementation(() => JSON.stringify(mockKeystoreData)),
existsSync: jest.fn().mockImplementation((fileName) => {
if (fileName === 'non-existent-file.txt') {
return false;
} else {
return true;
}
}),
writeFileSync: jest.fn(),
}));
jest.mock('../cli/logger');
import { Logger } from '../cli/logger';
const mockLogFn = jest.fn();
Logger.prototype.log = mockLogFn;
const mockErrFn = jest.fn();
Logger.prototype.error = mockErrFn;
import { Keystore } from '../cli/keystore';
import { show } from './show';
describe('Kibana keystore: show', () => {
const keystore = new Keystore('mock-path', '');
it('reads stored strings', () => {
const exitCode = show(keystore, 'foo', {});
expect(exitCode).toBe(0);
expect(mockLogFn).toHaveBeenCalledWith('turbo2000');
expect(mockErrFn).not.toHaveBeenCalled();
});
it('reads stored numbers', () => {
const exitCode = show(keystore, 'num', {});
expect(exitCode).toBe(0);
expect(mockLogFn).toHaveBeenCalledWith('12345');
expect(mockErrFn).not.toHaveBeenCalled();
});
it('reads stored objecs', () => {
const exitCode = show(keystore, 'bar', {});
expect(exitCode).toBe(0);
expect(mockLogFn).toHaveBeenCalledWith(JSON.stringify({ sub: 0 }));
expect(mockErrFn).not.toHaveBeenCalled();
});
it('outputs to a file when the arg is passed', () => {
const exitCode = show(keystore, 'foo', { output: 'non-existent-file.txt' });
expect(exitCode).toBe(0);
expect(mockLogFn).toHaveBeenCalledWith('Writing output to file: non-existent-file.txt');
expect(mockErrFn).not.toHaveBeenCalled();
});
it('logs and terminates with an error when output file exists', () => {
const exitCode = show(keystore, 'foo', { output: 'existing-file.txt' });
expect(exitCode).toBe(-1);
expect(mockErrFn).toHaveBeenCalledWith(
'ERROR: Output file already exists. Remove it before retrying.'
);
expect(mockLogFn).not.toHaveBeenCalled();
});
it("logs and terminates with an error when the store doesn't have the key", () => {
const exitCode = show(keystore, 'no-key');
expect(exitCode).toBe(-1);
expect(mockErrFn).toHaveBeenCalledWith("ERROR: Kibana keystore doesn't have requested key.");
expect(mockLogFn).not.toHaveBeenCalled();
});
afterEach(() => {
mockLogFn.mockReset();
mockErrFn.mockReset();
jest.restoreAllMocks();
});
});

62
src/cli_keystore/show.ts Normal file
View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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 { writeFileSync, existsSync } from 'fs';
import { Keystore } from '../cli/keystore';
import { Logger } from '../cli/logger';
interface ShowOptions {
silent?: boolean;
output?: string;
}
export function show(keystore: Keystore, key: string, options: ShowOptions = {}): number | void {
const { silent, output } = options;
const logger = new Logger({ silent });
if (!keystore.exists()) {
logger.error("ERROR: Kibana keystore not found. Use 'create' command to create one.");
return -1;
}
if (!keystore.has(key)) {
logger.error("ERROR: Kibana keystore doesn't have requested key.");
return -1;
}
const value = keystore.data[key];
const valueAsString = typeof value === 'string' ? value : JSON.stringify(value);
if (output) {
if (existsSync(output)) {
logger.error('ERROR: Output file already exists. Remove it before retrying.');
return -1;
} else {
writeFileSync(output, valueAsString);
logger.log('Writing output to file: ' + output);
}
} else {
logger.log(valueAsString);
}
return 0;
}
export function showCli(program: any, keystore: Keystore) {
program
.command('show <key>')
.description(
'Displays the value of a single setting in the keystore. Pass the -o (or --output) parameter to write the setting to a file.'
)
.option('-s, --silent', 'prevent all logging')
.option('-o, --output <file>', 'output value to a file')
.action((key: string, options: ShowOptions) => {
process.exitCode = show(keystore, key, options) || 0;
});
}

View file

@ -7,6 +7,7 @@
"keystore/**/*",
"utils/**/*",
"*.js",
"*.ts",
],
"kbn_references": [
{ "path": "../setup_node_env/tsconfig.json" },