Skip to content

Commit

Permalink
[cli] Add bin/kibana-encryption-keys (#82838)
Browse files Browse the repository at this point in the history
Co-authored-by: Aleh Zasypkin <[email protected]>
Co-authored-by: Tyler Smalley <[email protected]>
  • Loading branch information
3 people authored Nov 19, 2020
1 parent 7f962e5 commit 6c23302
Show file tree
Hide file tree
Showing 28 changed files with 606 additions and 27 deletions.
20 changes: 20 additions & 0 deletions scripts/kibana_encryption_keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

require('../src/cli_encryption_keys/dev');
23 changes: 23 additions & 0 deletions src/cli_encryption_keys/__snapshots__/interactive.test.js.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions src/cli_encryption_keys/cli_encryption_keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { pkg } from '../core/server/utils';
import Command from '../cli/command';
import { EncryptionConfig } from './encryption_config';

import { generateCli } from './generate';

const argv = process.env.kbnWorkerArgv
? JSON.parse(process.env.kbnWorkerArgv)
: process.argv.slice();
const program = new Command('bin/kibana-encryption-keys');

program.version(pkg.version).description('A tool for managing encryption keys');

const encryptionConfig = new EncryptionConfig();

generateCli(program, encryptionConfig);

program
.command('help <command>')
.description('Get the help for a specific command')
.action(function (cmdName) {
const cmd = Object.values(program.commands).find((command) => command._name === cmdName);
if (!cmd) return program.error(`unknown command ${cmdName}`);
cmd.help();
});

program.command('*', null, { noHelp: true }).action(function (cmd) {
program.error(`unknown command ${cmd}`);
});

// check for no command name
const subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//);
if (!subCommand) {
program.defaultHelp();
}

program.parse(process.argv);
21 changes: 21 additions & 0 deletions src/cli_encryption_keys/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

require('../setup_node_env');
require('./cli_encryption_keys');
21 changes: 21 additions & 0 deletions src/cli_encryption_keys/dist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

require('../setup_node_env/dist');
require('./cli_encryption_keys');
86 changes: 86 additions & 0 deletions src/cli_encryption_keys/encryption_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import crypto from 'crypto';
import { join } from 'path';
import { get } from 'lodash';
import { readFileSync } from 'fs';
import { safeLoad } from 'js-yaml';

import { getConfigDirectory } from '@kbn/utils';

export class EncryptionConfig {
#config = safeLoad(readFileSync(join(getConfigDirectory(), 'kibana.yml')));
#encryptionKeyPaths = [
'xpack.encryptedSavedObjects.encryptionKey',
'xpack.reporting.encryptionKey',
'xpack.security.encryptionKey',
];
#encryptionMeta = {
'xpack.encryptedSavedObjects.encryptionKey': {
docs:
'https://www.elastic.co/guide/en/kibana/current/xpack-security-secure-saved-objects.html#xpack-security-secure-saved-objects',
description: 'Used to encrypt stored objects such as dashboards and visualizations',
},
'xpack.reporting.encryptionKey': {
docs:
'https://www.elastic.co/guide/en/kibana/current/reporting-settings-kb.html#general-reporting-settings',
description: 'Used to encrypt saved reports',
},
'xpack.security.encryptionKey': {
docs:
'https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#security-session-and-cookie-settings',
description: 'Used to encrypt session information',
},
};

_getEncryptionKey(key) {
return get(this.#config, key);
}

_hasEncryptionKey(key) {
return !!get(this.#config, key);
}

_generateEncryptionKey() {
return crypto.randomBytes(16).toString('hex');
}

docs({ comment } = {}) {
const commentString = comment ? '#' : '';
let docs = '';
this.#encryptionKeyPaths.forEach((key) => {
docs += `${commentString}${key}
${commentString}${this.#encryptionMeta[key].description}
${commentString}${this.#encryptionMeta[key].docs}
\n`;
});
return docs;
}

generate({ force = false }) {
const output = {};
this.#encryptionKeyPaths.forEach((key) => {
if (force || !this._hasEncryptionKey(key)) {
output[key] = this._generateEncryptionKey();
}
});
return output;
}
}
83 changes: 83 additions & 0 deletions src/cli_encryption_keys/encryption_config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { EncryptionConfig } from './encryption_config';
import crypto from 'crypto';
import fs from 'fs';

describe('encryption key configuration', () => {
let encryptionConfig = null;

beforeEach(() => {
jest.spyOn(fs, 'readFileSync').mockReturnValue('xpack.security.encryptionKey: foo');
jest.spyOn(crypto, 'randomBytes').mockReturnValue('random-key');
encryptionConfig = new EncryptionConfig();
});
it('should be able to check for encryption keys', () => {
expect(encryptionConfig._hasEncryptionKey('xpack.reporting.encryptionKey')).toEqual(false);
expect(encryptionConfig._hasEncryptionKey('xpack.security.encryptionKey')).toEqual(true);
});

it('should be able to get encryption keys', () => {
expect(encryptionConfig._getEncryptionKey('xpack.reporting.encryptionKey')).toBeUndefined();
expect(encryptionConfig._getEncryptionKey('xpack.security.encryptionKey')).toEqual('foo');
});

it('should generate a key', () => {
expect(encryptionConfig._generateEncryptionKey()).toEqual('random-key');
});

it('should only generate unset keys', () => {
const output = encryptionConfig.generate({ force: false });
expect(output['xpack.security.encryptionKey']).toEqual(undefined);
expect(output['xpack.reporting.encryptionKey']).toEqual('random-key');
});

it('should regenerate all keys if the force flag is set', () => {
const output = encryptionConfig.generate({ force: true });
expect(output['xpack.security.encryptionKey']).toEqual('random-key');
expect(output['xpack.reporting.encryptionKey']).toEqual('random-key');
expect(output['xpack.encryptedSavedObjects.encryptionKey']).toEqual('random-key');
});

it('should set encryptedObjects and reporting with a default configuration', () => {
const output = encryptionConfig.generate({});
expect(output['xpack.security.encryptionKey']).toBeUndefined();
expect(output['xpack.encryptedSavedObjects.encryptionKey']).toEqual('random-key');
expect(output['xpack.reporting.encryptionKey']).toEqual('random-key');
});
});
59 changes: 59 additions & 0 deletions src/cli_encryption_keys/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { safeDump } from 'js-yaml';
import { isEmpty } from 'lodash';
import { interactive } from './interactive';
import { Logger } from '../cli_plugin/lib/logger';

export async function generate(encryptionConfig, command) {
const logger = new Logger();
const keys = encryptionConfig.generate({ force: command.force });
if (isEmpty(keys)) {
logger.log('No keys to write. Use the --force flag to generate new keys.');
} else {
if (!command.quiet) {
logger.log('## Kibana Encryption Key Generation Utility\n');
logger.log(
`The 'generate' command guides you through the process of setting encryption keys for:\n`
);
logger.log(encryptionConfig.docs());
logger.log(
'Already defined settings are ignored and can be regenerated using the --force flag. Check the documentation links for instructions on how to rotate encryption keys.'
);
logger.log('Definitions should be set in the kibana.yml used configure Kibana.\n');
}
if (command.interactive) {
await interactive(keys, encryptionConfig.docs({ comment: true }), logger);
} else {
if (!command.quiet) logger.log('Settings:');
logger.log(safeDump(keys));
}
}
}

export function generateCli(program, encryptionConfig) {
program
.command('generate')
.description('Generates encryption keys')
.option('-i, --interactive', 'interactive output')
.option('-q, --quiet', 'do not include instructions')
.option('-f, --force', 'generate new keys for all settings')
.action(generate.bind(null, encryptionConfig));
}
Loading

0 comments on commit 6c23302

Please sign in to comment.