mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Modifying SSL settings to be consistent with the stack
This commit is contained in:
parent
2ed90d9a8b
commit
6ea4077b45
59 changed files with 1335 additions and 307 deletions
|
@ -39,22 +39,23 @@
|
|||
#elasticsearch.username: "user"
|
||||
#elasticsearch.password: "pass"
|
||||
|
||||
# Paths to the PEM-format SSL certificate and SSL key files, respectively. These
|
||||
# files enable SSL for outgoing requests from the Kibana server to the browser.
|
||||
#server.ssl.cert: /path/to/your/server.crt
|
||||
# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
|
||||
# These settings enable SSL for outgoing requests from the Kibana server to the browser.
|
||||
#server.ssl.enabled: false
|
||||
#server.ssl.certificate: /path/to/your/server.crt
|
||||
#server.ssl.key: /path/to/your/server.key
|
||||
|
||||
# Optional settings that provide the paths to the PEM-format SSL certificate and key files.
|
||||
# These files validate that your Elasticsearch backend uses the same key files.
|
||||
#elasticsearch.ssl.cert: /path/to/your/client.crt
|
||||
#elasticsearch.ssl.certificate: /path/to/your/client.crt
|
||||
#elasticsearch.ssl.key: /path/to/your/client.key
|
||||
|
||||
# Optional setting that enables you to specify a path to the PEM file for the certificate
|
||||
# authority for your Elasticsearch instance.
|
||||
#elasticsearch.ssl.ca: /path/to/your/CA.pem
|
||||
#elasticsearch.ssl.certificateAuthorities: /path/to/your/CA.pem
|
||||
|
||||
# To disregard the validity of SSL certificates, change this setting's value to false.
|
||||
#elasticsearch.ssl.verify: true
|
||||
# To disregard the validity of SSL certificates, change this setting's value to 'none'.
|
||||
#elasticsearch.ssl.verificationMode: full
|
||||
|
||||
# Time in milliseconds to wait for Elasticsearch to respond to pings. Defaults to the value of
|
||||
# the elasticsearch.requestTimeout setting.
|
||||
|
|
|
@ -42,14 +42,15 @@ to work with X-Pack, see {xpack-ref}kibana.html.
|
|||
Kibana supports SSL encryption for both client requests and the requests the Kibana server
|
||||
sends to Elasticsearch.
|
||||
|
||||
To encrypt communications between the browser and the Kibana server, you configure the `ssl_key_file` and
|
||||
`ssl_cert_file` properties in `kibana.yml`:
|
||||
To encrypt communications between the browser and the Kibana server, you configure the `server.ssl.enabled`,
|
||||
`server.ssl.certificate` and `server.ssl.key` properties in `kibana.yml`:
|
||||
|
||||
[source,text]
|
||||
----
|
||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||
server.ssl.enabled: true
|
||||
server.ssl.key: /path/to/your/server.key
|
||||
server.ssl.cert: /path/to/your/server.crt
|
||||
server.ssl.certificate: /path/to/your/server.crt
|
||||
----
|
||||
|
||||
If you are using X-Pack Security or a proxy that provides an HTTPS endpoint for Elasticsearch,
|
||||
|
@ -61,17 +62,18 @@ protocol when you configure the Elasticsearch URL in `kibana.yml`:
|
|||
|
||||
[source,text]
|
||||
----
|
||||
elasticsearch: "https://<your_elasticsearch_host>.com:9200"
|
||||
elasticsearch.url: "https://<your_elasticsearch_host>.com:9200"
|
||||
----
|
||||
|
||||
If you are using a self-signed certificate for Elasticsearch, set the `ca` property in
|
||||
`kibana.yml` to specify the location of the PEM file. Setting the `ca` property lets you leave the `verify_ssl` option enabled.
|
||||
If you are using a self-signed certificate for Elasticsearch, set the `certificateAuthorities` property in
|
||||
`kibana.yml` to specify the location of the PEM file. Setting the `certificateAuthorities` property lets you use the
|
||||
default `verificationMode` option of `full`.
|
||||
|
||||
[source,text]
|
||||
----
|
||||
# If you need to provide a CA certificate for your Elasticsearch instance, put
|
||||
# the path of the pem file here.
|
||||
ca: /path/to/your/ca/cacert.pem
|
||||
elasticsearch.ssl.certificateAuthorities: /path/to/your/ca/cacert.pem
|
||||
----
|
||||
|
||||
[float]
|
||||
|
|
|
@ -33,14 +33,20 @@ Specify the position of the subdomain the URL with the token `{s}`.
|
|||
`elasticsearch.username:` and `elasticsearch.password:`:: If your Elasticsearch is protected with basic authentication,
|
||||
these settings provide the username and password that the Kibana server uses to perform maintenance on the Kibana index at
|
||||
startup. Your Kibana users still need to authenticate with Elasticsearch, which is proxied through the Kibana server.
|
||||
`server.ssl.cert:` and `server.ssl.key:`:: Paths to the PEM-format SSL certificate and SSL key files, respectively. These
|
||||
files enable SSL for outgoing requests from the Kibana server to the browser.
|
||||
`server.ssl.enabled`:: *Default: "false"* Enables SSL for outgoing requests from the Kibana server to the browser. When set to `true`, `server.ssl.certificate` and `server.ssl.key` are required
|
||||
`server.ssl.certificate:` and `server.ssl.key:`:: Paths to the PEM-format SSL certificate and SSL key files, respectively.
|
||||
`server.ssl.keyPassphrase`:: The passphrase that will be used to decrypt the private key. This value is optional as the key may not be encrypted.
|
||||
`server.ssl.certificateAuthorities`:: List of paths to PEM encoded certificate files that should be trusted.
|
||||
`server.ssl.clientAuthentication`:: *Default: none* Controls Kibana's server behavior in regard to requesting a certificate from client connections. Valid values are `required`,
|
||||
and `none`. `required` forces a client to present a certificate, while `none` does not.
|
||||
`server.ssl.supportedProtocols`:: *Default: TLSv1, TLSv1.1, TLSv1.2* Supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`
|
||||
`elasticsearch.ssl.cert:` and `elasticsearch.ssl.key:`:: Optional settings that provide the paths to the PEM-format SSL
|
||||
certificate and key files. These files validate that your Elasticsearch backend uses the same key files.
|
||||
`elasticsearch.ssl.ca:`:: Optional setting that enables you to specify a path to the PEM file for the certificate
|
||||
`elasticsearch.ssl.keyPassphrase`:: The passphrase that will be used to decrypt the private key. This value is optional as the key may not be encrypted.
|
||||
`elasticsearch.ssl.certificateAuthorities:`:: Optional setting that enables you to specify a list of paths to the PEM file for the certificate
|
||||
authority for your Elasticsearch instance.
|
||||
`elasticsearch.ssl.verify:`:: *Default: true* To disregard the validity of SSL certificates, change this setting’s value
|
||||
to `false`.
|
||||
`elasticsearch.ssl.verificationMode:`:: *Default: full* Controls the verification of certificates. Valid values are `none`, `certificate`, and `full`.
|
||||
`full` performs hostname verification, and `certificate` does not.
|
||||
`elasticsearch.pingTimeout:`:: *Default: the value of the `elasticsearch.requestTimeout` setting* Time in milliseconds to
|
||||
wait for Elasticsearch to respond to pings.
|
||||
`elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Server } from 'hapi';
|
||||
import { notFound } from 'boom';
|
||||
import { merge, sample } from 'lodash';
|
||||
import { map, merge, sample } from 'lodash';
|
||||
import { format as formatUrl } from 'url';
|
||||
import { map, fromNode } from 'bluebird';
|
||||
import { map as promiseMap, fromNode } from 'bluebird';
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
|
@ -10,6 +10,7 @@ import Config from '../../server/config/config';
|
|||
import setupConnection from '../../server/http/setup_connection';
|
||||
import registerHapiPlugins from '../../server/http/register_hapi_plugins';
|
||||
import setupLogging from '../../server/logging';
|
||||
import { transformDeprecations } from '../../server/config/transform_deprecations';
|
||||
import { DEV_SSL_CERT_PATH } from '../dev_ssl';
|
||||
|
||||
const alphabet = 'abcdefghijklmnopqrztuvwxyz'.split('');
|
||||
|
@ -19,20 +20,21 @@ export default class BasePathProxy {
|
|||
this.clusterManager = clusterManager;
|
||||
this.server = new Server();
|
||||
|
||||
const config = Config.withDefaultSchema(userSettings);
|
||||
const settings = transformDeprecations(userSettings);
|
||||
const config = Config.withDefaultSchema(settings);
|
||||
|
||||
this.targetPort = config.get('dev.basePathProxyTarget');
|
||||
this.basePath = config.get('server.basePath');
|
||||
|
||||
const { cert } = config.get('server.ssl');
|
||||
if (cert) {
|
||||
const httpsAgentConfig = {};
|
||||
if (cert === DEV_SSL_CERT_PATH && config.get('server.host') !== 'localhost') {
|
||||
httpsAgentConfig.rejectUnauthorized = false;
|
||||
} else {
|
||||
httpsAgentConfig.ca = readFileSync(cert);
|
||||
}
|
||||
this.proxyAgent = new HttpsAgent(httpsAgentConfig);
|
||||
const sslEnabled = config.get('server.ssl.enabled');
|
||||
if (sslEnabled) {
|
||||
this.proxyAgent = new HttpsAgent({
|
||||
key: readFileSync(config.get('server.ssl.key')),
|
||||
passphrase: config.get('server.ssl.keyPassphrase'),
|
||||
cert: readFileSync(config.get('server.ssl.certificate')),
|
||||
ca: map(config.get('server.ssl.certificateAuthorities'), readFileSync),
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.basePath) {
|
||||
|
@ -67,7 +69,7 @@ export default class BasePathProxy {
|
|||
config: {
|
||||
pre: [
|
||||
(req, reply) => {
|
||||
map(clusterManager.workers, worker => {
|
||||
promiseMap(clusterManager.workers, worker => {
|
||||
if (worker.type === 'server' && !worker.listening && !worker.crashed) {
|
||||
return fromNode(cb => {
|
||||
const done = () => {
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
import expect from 'expect.js';
|
||||
import { set } from 'lodash';
|
||||
import { checkForDeprecatedConfig } from '../deprecated_config';
|
||||
import sinon from 'auto-release-sinon';
|
||||
|
||||
describe('cli/serve/deprecated_config', function () {
|
||||
it('passes original config through', function () {
|
||||
const config = {};
|
||||
set(config, 'server.xsrf.token', 'xxtokenxx');
|
||||
const output = checkForDeprecatedConfig(config);
|
||||
expect(output).to.be(config);
|
||||
expect(output.server).to.be(config.server);
|
||||
expect(output.server.xsrf).to.be(config.server.xsrf);
|
||||
expect(output.server.xsrf.token).to.be(config.server.xsrf.token);
|
||||
});
|
||||
|
||||
it('logs warnings about deprecated config values', function () {
|
||||
const log = sinon.stub();
|
||||
const config = {};
|
||||
set(config, 'server.xsrf.token', 'xxtokenxx');
|
||||
checkForDeprecatedConfig(config, log);
|
||||
sinon.assert.calledOnce(log);
|
||||
expect(log.firstCall.args[0]).to.match(/server\.xsrf\.token.+deprecated/);
|
||||
});
|
||||
|
||||
describe('does not support compound.keys', function () {
|
||||
it('ignores fully compound keys', function () {
|
||||
const log = sinon.stub();
|
||||
const config = { 'server.xsrf.token': 'xxtokenxx' };
|
||||
checkForDeprecatedConfig(config, log);
|
||||
sinon.assert.notCalled(log);
|
||||
});
|
||||
|
||||
it('ignores partially compound keys', function () {
|
||||
const log = sinon.stub();
|
||||
const config = { server: { 'xsrf.token': 'xxtokenxx' } };
|
||||
checkForDeprecatedConfig(config, log);
|
||||
sinon.assert.notCalled(log);
|
||||
});
|
||||
|
||||
it('ignores partially compound keys', function () {
|
||||
const log = sinon.stub();
|
||||
const config = { 'server.xsrf': { token: 'xxtokenxx' } };
|
||||
checkForDeprecatedConfig(config, log);
|
||||
sinon.assert.notCalled(log);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
server.xsrf.token: token
|
|
@ -1 +0,0 @@
|
|||
kibana_index: indexname
|
|
@ -1,28 +0,0 @@
|
|||
import expect from 'expect.js';
|
||||
import { rewriteLegacyConfig } from '../legacy_config';
|
||||
import sinon from 'auto-release-sinon';
|
||||
|
||||
describe('cli/serve/legacy_config', function () {
|
||||
it('returns a clone of the input', function () {
|
||||
const file = {};
|
||||
const output = rewriteLegacyConfig(file);
|
||||
expect(output).to.not.be(file);
|
||||
});
|
||||
|
||||
it('rewrites legacy config values with literal path replacement', function () {
|
||||
const file = { port: 4000, host: 'kibana.com' };
|
||||
const output = rewriteLegacyConfig(file);
|
||||
expect(output).to.not.be(file);
|
||||
expect(output).to.eql({
|
||||
'server.port': 4000,
|
||||
'server.host': 'kibana.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('logs warnings when legacy config properties are encountered', function () {
|
||||
const log = sinon.stub();
|
||||
rewriteLegacyConfig({ port: 5555 }, log);
|
||||
sinon.assert.calledOnce(log);
|
||||
expect(log.firstCall.args[0]).to.match(/port.+deprecated.+server\.port/);
|
||||
});
|
||||
});
|
|
@ -57,46 +57,4 @@ describe('cli/serve/read_yaml_config', function () {
|
|||
process.chdir(oldCwd);
|
||||
});
|
||||
});
|
||||
|
||||
context('stubbed stdout', function () {
|
||||
let stub;
|
||||
|
||||
beforeEach(function () {
|
||||
stub = sinon.stub(process.stdout, 'write');
|
||||
});
|
||||
|
||||
context('deprecated settings', function () {
|
||||
it('warns about deprecated settings', function () {
|
||||
readYamlConfig(fixture('deprecated.yml'));
|
||||
sinon.assert.calledOnce(stub);
|
||||
expect(stub.firstCall.args[0]).to.match(/deprecated/);
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('only warns once about deprecated settings', function () {
|
||||
readYamlConfig(fixture('deprecated.yml'));
|
||||
readYamlConfig(fixture('deprecated.yml'));
|
||||
readYamlConfig(fixture('deprecated.yml'));
|
||||
sinon.assert.notCalled(stub); // already logged in previous test
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
context('legacy settings', function () {
|
||||
it('warns about deprecated settings', function () {
|
||||
readYamlConfig(fixture('legacy.yml'));
|
||||
sinon.assert.calledOnce(stub);
|
||||
expect(stub.firstCall.args[0]).to.match(/has been replaced/);
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('only warns once about legacy settings', function () {
|
||||
readYamlConfig(fixture('legacy.yml'));
|
||||
readYamlConfig(fixture('legacy.yml'));
|
||||
readYamlConfig(fixture('legacy.yml'));
|
||||
sinon.assert.notCalled(stub); // already logged in previous test
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { forOwn, has, noop } from 'lodash';
|
||||
|
||||
// deprecated settings are still allowed, but will be removed at a later time. They
|
||||
// are checked for after the config object is prepared and known, so legacySettings
|
||||
// will have already been transformed.
|
||||
export const deprecatedSettings = new Map([
|
||||
[['server', 'xsrf', 'token'], 'server.xsrf.token is deprecated. It is no longer used when providing xsrf protection.']
|
||||
]);
|
||||
|
||||
// check for and warn about deprecated settings
|
||||
export function checkForDeprecatedConfig(object, log = noop) {
|
||||
for (const [key, msg] of deprecatedSettings.entries()) {
|
||||
if (has(object, key)) log(msg);
|
||||
}
|
||||
return object;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import { noop, transform } from 'lodash';
|
||||
|
||||
// legacySettings allow kibana 4.2+ to accept the same config file that people
|
||||
// used for kibana 4.0 and 4.1. These settings are transformed to their modern
|
||||
// equivalents at the very begining of the process
|
||||
export const legacySettings = {
|
||||
// server
|
||||
port: 'server.port',
|
||||
host: 'server.host',
|
||||
pid_file: 'pid.file',
|
||||
ssl_cert_file: 'server.ssl.cert',
|
||||
ssl_key_file: 'server.ssl.key',
|
||||
|
||||
// logging
|
||||
log_file: 'logging.dest',
|
||||
|
||||
// kibana
|
||||
kibana_index: 'kibana.index',
|
||||
default_app_id: 'kibana.defaultAppId',
|
||||
|
||||
// es
|
||||
ca: 'elasticsearch.ssl.ca',
|
||||
elasticsearch_preserve_host: 'elasticsearch.preserveHost',
|
||||
elasticsearch_url: 'elasticsearch.url',
|
||||
kibana_elasticsearch_client_crt: 'elasticsearch.ssl.cert',
|
||||
kibana_elasticsearch_client_key: 'elasticsearch.ssl.key',
|
||||
kibana_elasticsearch_password: 'elasticsearch.password',
|
||||
kibana_elasticsearch_username: 'elasticsearch.username',
|
||||
ping_timeout: 'elasticsearch.pingTimeout',
|
||||
request_timeout: 'elasticsearch.requestTimeout',
|
||||
shard_timeout: 'elasticsearch.shardTimeout',
|
||||
startup_timeout: 'elasticsearch.startupTimeout',
|
||||
tilemap_url: 'tilemap.url',
|
||||
tilemap_min_zoom: 'tilemap.options.minZoom',
|
||||
tilemap_max_zoom: 'tilemap.options.maxZoom',
|
||||
tilemap_attribution: 'tilemap.options.attribution',
|
||||
tilemap_subdomains: 'tilemap.options.subdomains',
|
||||
verify_ssl: 'elasticsearch.ssl.verify',
|
||||
};
|
||||
|
||||
// transform legacy options into new namespaced versions
|
||||
export function rewriteLegacyConfig(object, log = noop) {
|
||||
return transform(object, (clone, val, key) => {
|
||||
if (legacySettings.hasOwnProperty(key)) {
|
||||
const replacement = legacySettings[key];
|
||||
log(`Config key "${key}" is deprecated. It has been replaced with "${replacement}"`);
|
||||
clone[replacement] = val;
|
||||
} else {
|
||||
clone[key] = val;
|
||||
}
|
||||
}, {});
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
import { chain, isArray, isPlainObject, forOwn, memoize, set, transform } from 'lodash';
|
||||
import { isArray, isPlainObject, forOwn, set, transform } from 'lodash';
|
||||
import { readFileSync as read } from 'fs';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import { red } from 'ansicolors';
|
||||
|
||||
|
||||
import { fromRoot } from '../../utils';
|
||||
import { rewriteLegacyConfig } from './legacy_config';
|
||||
import { checkForDeprecatedConfig } from './deprecated_config';
|
||||
|
||||
const log = memoize(function (message) {
|
||||
console.log(red('WARNING:'), message);
|
||||
});
|
||||
|
||||
export function merge(sources) {
|
||||
return transform(sources, (merged, source) => {
|
||||
|
@ -35,6 +29,5 @@ export function merge(sources) {
|
|||
export default function (paths) {
|
||||
const files = [].concat(paths || []);
|
||||
const yamls = files.map(path => safeLoad(read(path, 'utf8')));
|
||||
const config = merge(yamls.map(file => rewriteLegacyConfig(file, log)));
|
||||
return checkForDeprecatedConfig(config, log);
|
||||
return merge(yamls);
|
||||
}
|
||||
|
|
|
@ -38,8 +38,13 @@ function readServerSettings(opts, extraCliOptions) {
|
|||
if (opts.dev) {
|
||||
set('env', 'development');
|
||||
set('optimize.lazy', true);
|
||||
if (opts.ssl && !has('server.ssl.cert') && !has('server.ssl.key')) {
|
||||
set('server.ssl.cert', DEV_SSL_CERT_PATH);
|
||||
|
||||
if (opts.ssl) {
|
||||
set('server.ssl.enabled', true);
|
||||
}
|
||||
|
||||
if (opts.ssl && !has('server.ssl.certificate') && !has('server.ssl.key')) {
|
||||
set('server.ssl.certificate', DEV_SSL_CERT_PATH);
|
||||
set('server.ssl.key', DEV_SSL_KEY_PATH);
|
||||
}
|
||||
}
|
||||
|
|
56
src/core_plugins/console/__tests__/index.js
Normal file
56
src/core_plugins/console/__tests__/index.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Deprecations } from '../../../deprecation';
|
||||
import expect from 'expect.js';
|
||||
import index from '../index';
|
||||
import { noop } from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('plugins/console', function () {
|
||||
describe('#deprecate()', function () {
|
||||
let transformDeprecations;
|
||||
|
||||
before(function () {
|
||||
const Plugin = function (options) {
|
||||
this.deprecations = options.deprecations;
|
||||
};
|
||||
|
||||
const plugin = index({ Plugin });
|
||||
|
||||
const deprecations = plugin.deprecations(Deprecations);
|
||||
transformDeprecations = (settings, log = noop) => {
|
||||
deprecations.forEach(deprecation => deprecation(settings, log));
|
||||
};
|
||||
});
|
||||
|
||||
context('proxyConfig', function () {
|
||||
it('leaves the proxyConfig settings', function () {
|
||||
const proxyConfigOne = {};
|
||||
const proxyConfigTwo = {};
|
||||
const settings = {
|
||||
proxyConfig: [proxyConfigOne, proxyConfigTwo]
|
||||
};
|
||||
|
||||
transformDeprecations(settings);
|
||||
expect(settings.proxyConfig[0]).to.be(proxyConfigOne);
|
||||
expect(settings.proxyConfig[1]).to.be(proxyConfigTwo);
|
||||
});
|
||||
|
||||
it('logs a warning when proxyConfig is specified', function () {
|
||||
const settings = {
|
||||
proxyConfig: []
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it(`doesn't log a warning when proxyConfig isn't specified`, function () {
|
||||
const settings = {};
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,8 +3,9 @@ import Boom from 'boom';
|
|||
import apiServer from './api_server/server';
|
||||
import { existsSync } from 'fs';
|
||||
import { resolve, join, sep } from 'path';
|
||||
import { startsWith, endsWith } from 'lodash';
|
||||
import { has, startsWith, endsWith } from 'lodash';
|
||||
import { ProxyConfigCollection } from './server/proxy_config_collection';
|
||||
import { getElasticsearchProxyConfig } from './server/elasticsearch_proxy_config';
|
||||
|
||||
export default function (kibana) {
|
||||
const modules = resolve(__dirname, 'public/webpackShims/');
|
||||
|
@ -50,24 +51,20 @@ export default function (kibana) {
|
|||
key: Joi.string()
|
||||
}).default()
|
||||
})
|
||||
).default([
|
||||
{
|
||||
match: {
|
||||
protocol: '*',
|
||||
host: '*',
|
||||
port: '*',
|
||||
path: '*'
|
||||
},
|
||||
|
||||
timeout: 180000,
|
||||
ssl: {
|
||||
verify: true
|
||||
}
|
||||
}
|
||||
])
|
||||
).default()
|
||||
}).default();
|
||||
},
|
||||
|
||||
deprecations: function () {
|
||||
return [
|
||||
(settings, log) => {
|
||||
if (has(settings, 'proxyConfig')) {
|
||||
log('Config key "proxyConfig" is deprecated. Configuration can be inferred from the "elasticsearch" settings');
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
init: function (server, options) {
|
||||
const filters = options.proxyFilter.map(str => new RegExp(str));
|
||||
|
||||
|
@ -108,6 +105,14 @@ export default function (kibana) {
|
|||
|
||||
const requestHeadersWhitelist = server.config().get('elasticsearch.requestHeadersWhitelist');
|
||||
const filterHeaders = server.plugins.elasticsearch.filterHeaders;
|
||||
|
||||
let additionalConfig;
|
||||
if (server.config().get('console.proxyConfig')) {
|
||||
additionalConfig = proxyConfigCollection.configForUri(uri);
|
||||
} else {
|
||||
additionalConfig = getElasticsearchProxyConfig(server);
|
||||
}
|
||||
|
||||
reply.proxy({
|
||||
mapUri: function (request, done) {
|
||||
done(null, uri, filterHeaders(request.headers, requestHeadersWhitelist));
|
||||
|
@ -121,7 +126,7 @@ export default function (kibana) {
|
|||
}
|
||||
},
|
||||
|
||||
...proxyConfigCollection.configForUri(uri)
|
||||
...additionalConfig
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import expect from 'expect.js';
|
||||
import { getElasticsearchProxyConfig } from '../elasticsearch_proxy_config';
|
||||
import https from 'https';
|
||||
import http from 'http';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('plugins/console', function () {
|
||||
describe('#getElasticsearchProxyConfig', function () {
|
||||
|
||||
let server;
|
||||
|
||||
beforeEach(function () {
|
||||
const stub = sinon.stub();
|
||||
server = {
|
||||
config() {
|
||||
return {
|
||||
get: stub
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
server.config().get.withArgs('elasticsearch.url').returns('http://localhost:9200');
|
||||
server.config().get.withArgs('elasticsearch.ssl.verificationMode').returns('full');
|
||||
});
|
||||
|
||||
const setElasticsearchConfig = (key, value) => {
|
||||
server.config().get.withArgs(`elasticsearch.${key}`).returns(value);
|
||||
};
|
||||
|
||||
it('sets timeout', function () {
|
||||
const value = 1000;
|
||||
setElasticsearchConfig('requestTimeout', value);
|
||||
const proxyConfig = getElasticsearchProxyConfig(server);
|
||||
expect(proxyConfig.timeout).to.be(value);
|
||||
});
|
||||
|
||||
it(`uses https.Agent when url's protocol is https`, function () {
|
||||
setElasticsearchConfig('url', 'https://localhost:9200');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent).to.be.a(https.Agent);
|
||||
});
|
||||
|
||||
it(`uses http.Agent when url's protocol is http`, function () {
|
||||
setElasticsearchConfig('url', 'http://localhost:9200');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent).to.be.a(http.Agent);
|
||||
});
|
||||
|
||||
context('ssl', function () {
|
||||
beforeEach(function () {
|
||||
setElasticsearchConfig('url', 'https://localhost:9200');
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to false when verificationMode is none', function () {
|
||||
setElasticsearchConfig('ssl.verificationMode', 'none');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent.options.rejectUnauthorized).to.be(false);
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to true when verificationMode is certificate', function () {
|
||||
setElasticsearchConfig('ssl.verificationMode', 'certificate');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent.options.rejectUnauthorized).to.be(true);
|
||||
});
|
||||
|
||||
it('sets checkServerIdentity to not check hostname when verificationMode is certificate', function () {
|
||||
setElasticsearchConfig('ssl.verificationMode', 'certificate');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
|
||||
const cert = {
|
||||
subject: {
|
||||
CN: 'wrong.com'
|
||||
}
|
||||
};
|
||||
|
||||
expect(agent.options.checkServerIdentity).withArgs('right.com', cert).to.not.throwException();
|
||||
const result = agent.options.checkServerIdentity('right.com', cert);
|
||||
expect(result).to.be(undefined);
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to true when verificationMode is full', function () {
|
||||
setElasticsearchConfig('ssl.verificationMode', 'full');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
|
||||
expect(agent.options.rejectUnauthorized).to.be(true);
|
||||
});
|
||||
|
||||
it(`doesn't set checkServerIdentity when verificationMode is full`, function () {
|
||||
setElasticsearchConfig('ssl.verificationMode', 'full');
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
|
||||
expect(agent.options.checkServerIdentity).to.be(undefined);
|
||||
});
|
||||
|
||||
it(`sets ca when certificateAuthorities are specified`, function () {
|
||||
setElasticsearchConfig('ssl.certificateAuthorities', [__dirname + '/fixtures/ca.crt']);
|
||||
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent.options.ca).to.contain('test ca certificate\n');
|
||||
});
|
||||
|
||||
it(`sets cert and key when certificate and key paths are specified`, function () {
|
||||
setElasticsearchConfig('ssl.certificate', __dirname + '/fixtures/cert.crt');
|
||||
setElasticsearchConfig('ssl.key', __dirname + '/fixtures/cert.key');
|
||||
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent.options.cert).to.be('test certificate\n');
|
||||
expect(agent.options.key).to.be('test key\n');
|
||||
});
|
||||
|
||||
it(`sets passphrase when certificate, key and keyPassphrase are specified`, function () {
|
||||
setElasticsearchConfig('ssl.certificate', __dirname + '/fixtures/cert.crt');
|
||||
setElasticsearchConfig('ssl.key', __dirname + '/fixtures/cert.key');
|
||||
setElasticsearchConfig('ssl.keyPassphrase', 'secret');
|
||||
|
||||
const { agent } = getElasticsearchProxyConfig(server);
|
||||
expect(agent.options.passphrase).to.be('secret');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
test ca certificate
|
|
@ -0,0 +1 @@
|
|||
test certificate
|
|
@ -0,0 +1 @@
|
|||
test key
|
|
@ -0,0 +1,54 @@
|
|||
import _ from 'lodash';
|
||||
import { readFileSync } from 'fs';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import url from 'url';
|
||||
|
||||
const readFile = (file) => readFileSync(file, 'utf8');
|
||||
|
||||
const createAgent = (server) => {
|
||||
const config = server.config();
|
||||
const target = url.parse(config.get('elasticsearch.url'));
|
||||
|
||||
if (!/^https/.test(target.protocol)) return new http.Agent();
|
||||
|
||||
const agentOptions = {};
|
||||
|
||||
const verificationMode = config.get('elasticsearch.ssl.verificationMode');
|
||||
switch (verificationMode) {
|
||||
case 'none':
|
||||
agentOptions.rejectUnauthorized = false;
|
||||
break;
|
||||
case 'certificate':
|
||||
agentOptions.rejectUnauthorized = true;
|
||||
|
||||
// by default, NodeJS is checking the server identify
|
||||
agentOptions.checkServerIdentity = _.noop;
|
||||
break;
|
||||
case 'full':
|
||||
agentOptions.rejectUnauthorized = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown ssl verificationMode: ${verificationMode}`);
|
||||
}
|
||||
|
||||
if (_.size(config.get('elasticsearch.ssl.certificateAuthorities'))) {
|
||||
agentOptions.ca = config.get('elasticsearch.ssl.certificateAuthorities').map(readFile);
|
||||
}
|
||||
|
||||
// Add client certificate and key if required by elasticsearch
|
||||
if (config.get('elasticsearch.ssl.certificate') && config.get('elasticsearch.ssl.key')) {
|
||||
agentOptions.cert = readFile(config.get('elasticsearch.ssl.certificate'));
|
||||
agentOptions.key = readFile(config.get('elasticsearch.ssl.key'));
|
||||
agentOptions.passphrase = config.get('elasticsearch.ssl.keyPassphrase');
|
||||
}
|
||||
|
||||
return new https.Agent(agentOptions);
|
||||
};
|
||||
|
||||
export const getElasticsearchProxyConfig = (server) => {
|
||||
return {
|
||||
timeout: server.config().get('elasticsearch.requestTimeout'),
|
||||
agent: createAgent(server)
|
||||
};
|
||||
};
|
84
src/core_plugins/elasticsearch/__tests__/index.js
Normal file
84
src/core_plugins/elasticsearch/__tests__/index.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { Deprecations } from '../../../deprecation';
|
||||
import expect from 'expect.js';
|
||||
import index from '../index';
|
||||
import { compact, noop, set } from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('#deprecations()', function () {
|
||||
let transformDeprecations;
|
||||
|
||||
before(function () {
|
||||
const Plugin = function (options) {
|
||||
this.deprecations = options.deprecations;
|
||||
};
|
||||
|
||||
const plugin = index({ Plugin });
|
||||
|
||||
const deprecations = plugin.deprecations(Deprecations);
|
||||
transformDeprecations = (settings, log = noop) => {
|
||||
deprecations.forEach(deprecation => deprecation(settings, log));
|
||||
};
|
||||
});
|
||||
|
||||
[null, 'tribe'].forEach((basePath) => {
|
||||
const getKey = (path) => {
|
||||
return compact([basePath, path]).join('.');
|
||||
};
|
||||
|
||||
context(getKey('ssl.verificationMode'), function () {
|
||||
let settings;
|
||||
let sslSettings;
|
||||
|
||||
beforeEach(function () {
|
||||
settings = {};
|
||||
sslSettings = {};
|
||||
set(settings, getKey('ssl'), sslSettings);
|
||||
});
|
||||
|
||||
it(`sets verificationMode to none when verify is false`, function () {
|
||||
sslSettings.verify = false;
|
||||
|
||||
transformDeprecations(settings);
|
||||
expect(sslSettings.verificationMode).to.be('none');
|
||||
expect(sslSettings.verify).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should log when deprecating verify from false', function () {
|
||||
sslSettings.verify = false;
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it('sets verificationMode to full when verify is true', function () {
|
||||
sslSettings.verify = true;
|
||||
|
||||
transformDeprecations(settings);
|
||||
expect(sslSettings.verificationMode).to.be('full');
|
||||
expect(sslSettings.verify).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should log when deprecating verify from true', function () {
|
||||
sslSettings.verify = true;
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it(`shouldn't set verificationMode when verify isn't present`, function () {
|
||||
transformDeprecations(settings);
|
||||
expect(sslSettings.verificationMode).to.be(undefined);
|
||||
});
|
||||
|
||||
it(`shouldn't log when verify isn't present`, function () {
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
import { trim, trimRight, bindKey, get } from 'lodash';
|
||||
import { bindKey, compact, get, has, set, trim, trimRight } from 'lodash';
|
||||
import { unset } from '../../utils';
|
||||
import { methodNotAllowed } from 'boom';
|
||||
|
||||
import healthCheck from './lib/health_check';
|
||||
|
@ -19,6 +20,14 @@ module.exports = function ({ Plugin }) {
|
|||
config(Joi) {
|
||||
const { array, boolean, number, object, string, ref } = Joi;
|
||||
|
||||
const sslSchema = object({
|
||||
verificationMode: string().valid('none', 'certificate', 'full').default('full'),
|
||||
certificateAuthorities: array().single().items(string()),
|
||||
certificate: string(),
|
||||
key: string(),
|
||||
keyPassphrase: string()
|
||||
}).default();
|
||||
|
||||
return object({
|
||||
enabled: boolean().default(true),
|
||||
url: string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||
|
@ -32,12 +41,7 @@ module.exports = function ({ Plugin }) {
|
|||
pingTimeout: number().default(ref('requestTimeout')),
|
||||
startupTimeout: number().default(5000),
|
||||
logQueries: boolean().default(false),
|
||||
ssl: object({
|
||||
verify: boolean().default(true),
|
||||
ca: array().single().items(string()),
|
||||
cert: string(),
|
||||
key: string()
|
||||
}).default(),
|
||||
ssl: sslSchema,
|
||||
apiVersion: Joi.string().default('master'),
|
||||
healthCheck: object({
|
||||
delay: number().default(2500)
|
||||
|
@ -54,17 +58,43 @@ module.exports = function ({ Plugin }) {
|
|||
pingTimeout: number().default(ref('requestTimeout')),
|
||||
startupTimeout: number().default(5000),
|
||||
logQueries: boolean().default(false),
|
||||
ssl: object({
|
||||
verify: boolean().default(true),
|
||||
ca: array().single().items(string()),
|
||||
cert: string(),
|
||||
key: string()
|
||||
}).default(),
|
||||
ssl: sslSchema,
|
||||
apiVersion: Joi.string().default('master'),
|
||||
}).default()
|
||||
}).default();
|
||||
},
|
||||
|
||||
deprecations({ rename }) {
|
||||
const sslVerify = (basePath) => {
|
||||
const getKey = (path) => {
|
||||
return compact([basePath, path]).join('.');
|
||||
};
|
||||
|
||||
return (settings, log) => {
|
||||
const sslSettings = get(settings, getKey('ssl'));
|
||||
|
||||
if (!has(sslSettings, 'verify')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const verificationMode = get(sslSettings, 'verify') ? 'full' : 'none';
|
||||
set(sslSettings, 'verificationMode', verificationMode);
|
||||
unset(sslSettings, 'verify');
|
||||
|
||||
log(`Config key "${getKey('ssl.verify')}" is deprecated. It has been replaced with "${getKey('ssl.verificationMode')}"`);
|
||||
};
|
||||
};
|
||||
|
||||
return [
|
||||
rename('ssl.ca', 'ssl.certificateAuthorities'),
|
||||
rename('ssl.cert', 'ssl.certificate'),
|
||||
sslVerify(),
|
||||
rename('tribe.ssl.ca', 'tribe.ssl.certificateAuthorities'),
|
||||
rename('tribe.ssl.cert', 'tribe.ssl.certificate'),
|
||||
sslVerify('tribe')
|
||||
];
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
injectDefaultVars(server, options) {
|
||||
return {
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('plugins/elasticsearch', function () {
|
|||
let cluster;
|
||||
const config = {
|
||||
url: 'http://localhost:9200',
|
||||
ssl: { verify: false },
|
||||
ssl: { verificationMode: 'full' },
|
||||
requestHeadersWhitelist: [ 'authorization' ]
|
||||
};
|
||||
|
||||
|
|
39
src/core_plugins/elasticsearch/lib/__tests__/create_agent.js
Normal file
39
src/core_plugins/elasticsearch/lib/__tests__/create_agent.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import expect from 'expect.js';
|
||||
import createAgent from '../create_agent';
|
||||
import https from 'https';
|
||||
import http from 'http';
|
||||
import { set } from 'lodash';
|
||||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('lib/create_agent', function () {
|
||||
|
||||
it(`uses http.Agent when url's protocol is http`, function () {
|
||||
const config = {
|
||||
url: 'http://localhost:9200'
|
||||
};
|
||||
|
||||
const agent = createAgent(config);
|
||||
expect(agent).to.be.a(http.Agent);
|
||||
});
|
||||
|
||||
it(`throws an Error when url's protocol is https and ssl.verificationMode isn't set`, function () {
|
||||
const config = {
|
||||
url: 'https://localhost:9200'
|
||||
};
|
||||
|
||||
expect(createAgent).withArgs(config).to.throwException();
|
||||
});
|
||||
|
||||
it(`uses https.Agent when url's protocol is https and ssl.verificationMode is full`, function () {
|
||||
const config = {
|
||||
url: 'https://localhost:9200',
|
||||
ssl: {
|
||||
verificationMode: 'full'
|
||||
}
|
||||
};
|
||||
|
||||
const agent = createAgent(config);
|
||||
expect(agent).to.be.a(https.Agent);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -25,7 +25,7 @@ describe('plugins/elasticsearch', function () {
|
|||
const config = {
|
||||
url: 'http://localhost:9200',
|
||||
ssl: {
|
||||
verify: false
|
||||
verificationMode: 'none'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
test ca certificate
|
|
@ -0,0 +1 @@
|
|||
test certificate
|
|
@ -0,0 +1 @@
|
|||
test key
|
91
src/core_plugins/elasticsearch/lib/__tests__/parse_config.js
Normal file
91
src/core_plugins/elasticsearch/lib/__tests__/parse_config.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
import expect from 'expect.js';
|
||||
import { parseConfig } from '../parse_config';
|
||||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('lib/parse_config', function () {
|
||||
context('ssl', function () {
|
||||
let serverConfig;
|
||||
|
||||
beforeEach(function () {
|
||||
serverConfig = {
|
||||
url: 'https://localhost:9200',
|
||||
ssl: {
|
||||
verificationMode: 'full'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('throws an Exception when verificationMode is undefined', function () {
|
||||
delete serverConfig.ssl.verificationMode;
|
||||
|
||||
expect(parseConfig).withArgs(serverConfig).to.throwException();
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to false when verificationMode is none', function () {
|
||||
serverConfig.ssl.verificationMode = 'none';
|
||||
const config = parseConfig(serverConfig);
|
||||
expect(config.ssl.rejectUnauthorized).to.be(false);
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to true when verificationMode is certificate', function () {
|
||||
serverConfig.ssl.verificationMode = 'certificate';
|
||||
const config = parseConfig(serverConfig);
|
||||
expect(config.ssl.rejectUnauthorized).to.be(true);
|
||||
});
|
||||
|
||||
it('sets checkServerIdentity to not check hostname when verificationMode is certificate', function () {
|
||||
serverConfig.ssl.verificationMode = 'certificate';
|
||||
const config = parseConfig(serverConfig);
|
||||
|
||||
const cert = {
|
||||
subject: {
|
||||
CN: 'wrong.com'
|
||||
}
|
||||
};
|
||||
|
||||
expect(config.ssl.checkServerIdentity).withArgs('right.com', cert).to.not.throwException();
|
||||
const result = config.ssl.checkServerIdentity('right.com', cert);
|
||||
expect(result).to.be(undefined);
|
||||
});
|
||||
|
||||
it('sets rejectUnauthorized to true when verificationMode is full', function () {
|
||||
serverConfig.ssl.verificationMode = 'full';
|
||||
const config = parseConfig(serverConfig);
|
||||
|
||||
expect(config.ssl.rejectUnauthorized).to.be(true);
|
||||
});
|
||||
|
||||
it(`doesn't set checkServerIdentity when verificationMode is full`, function () {
|
||||
serverConfig.ssl.verificationMode = 'full';
|
||||
const config = parseConfig(serverConfig);
|
||||
|
||||
expect(config.ssl.checkServerIdentity).to.be(undefined);
|
||||
});
|
||||
|
||||
it(`sets ca when certificateAuthorities are specified`, function () {
|
||||
serverConfig.ssl.certificateAuthorities = [__dirname + '/fixtures/ca.crt'];
|
||||
|
||||
const config = parseConfig(serverConfig);
|
||||
expect(config.ssl.ca).to.contain('test ca certificate\n');
|
||||
});
|
||||
|
||||
it(`sets cert and key when certificate and key paths are specified`, function () {
|
||||
serverConfig.ssl.certificate = __dirname + '/fixtures/cert.crt';
|
||||
serverConfig.ssl.key = __dirname + '/fixtures/cert.key';
|
||||
|
||||
const config = parseConfig(serverConfig);
|
||||
expect(config.ssl.cert).to.be('test certificate\n');
|
||||
expect(config.ssl.key).to.be('test key\n');
|
||||
});
|
||||
|
||||
it(`sets passphrase when certificate, key and keyPassphrase are specified`, function () {
|
||||
serverConfig.ssl.certificate = __dirname + '/fixtures/cert.crt';
|
||||
serverConfig.ssl.key = __dirname + '/fixtures/cert.key';
|
||||
serverConfig.ssl.keyPassphrase = 'secret';
|
||||
|
||||
const config = parseConfig(serverConfig);
|
||||
expect(config.ssl.passphrase).to.be('secret');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import util from 'util';
|
||||
import url from 'url';
|
||||
import { get, size, pick } from 'lodash';
|
||||
import { get, noop, size, pick } from 'lodash';
|
||||
import { readFileSync } from 'fs';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
|
@ -30,15 +30,35 @@ export function parseConfig(serverConfig = {}) {
|
|||
}
|
||||
|
||||
// SSL
|
||||
config.ssl = { rejectUnauthorized: get(serverConfig, 'ssl.verify') };
|
||||
config.ssl = {};
|
||||
|
||||
if (get(serverConfig, 'ssl.cert') && get(serverConfig, 'ssl.key')) {
|
||||
config.ssl.cert = readFile(serverConfig.ssl.cert);
|
||||
config.ssl.key = readFile(serverConfig.ssl.key);
|
||||
const verificationMode = get(serverConfig, 'ssl.verificationMode');
|
||||
switch (verificationMode) {
|
||||
case 'none':
|
||||
config.ssl.rejectUnauthorized = false;
|
||||
break;
|
||||
case 'certificate':
|
||||
config.ssl.rejectUnauthorized = true;
|
||||
|
||||
// by default, NodeJS is checking the server identify
|
||||
config.ssl.checkServerIdentity = noop;
|
||||
break;
|
||||
case 'full':
|
||||
config.ssl.rejectUnauthorized = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown ssl verificationMode: ${verificationMode}`);
|
||||
}
|
||||
|
||||
if (size(get(serverConfig, 'ssl.ca'))) {
|
||||
config.ssl.ca = serverConfig.ssl.ca.map(readFile);
|
||||
if (size(get(serverConfig, 'ssl.certificateAuthorities'))) {
|
||||
config.ssl.ca = serverConfig.ssl.certificateAuthorities.map(readFile);
|
||||
}
|
||||
|
||||
// Add client certificate and key if required by elasticsearch
|
||||
if (get(serverConfig, 'ssl.certificate') && get(serverConfig, 'ssl.key')) {
|
||||
config.ssl.cert = readFile(serverConfig.ssl.certificate);
|
||||
config.ssl.key = readFile(serverConfig.ssl.key);
|
||||
config.ssl.passphrase = serverConfig.ssl.keyPassphrase;
|
||||
}
|
||||
|
||||
config.defer = () => Bluebird.defer();
|
||||
|
|
39
src/deprecation/__tests__/create_transform.js
Normal file
39
src/deprecation/__tests__/create_transform.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import createTransform from '../create_transform';
|
||||
import expect from 'expect.js';
|
||||
import { noop } from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('deprecation', function () {
|
||||
describe('createTransform', function () {
|
||||
it(`doesn't modify settings parameter`, function () {
|
||||
const settings = {
|
||||
original: true
|
||||
};
|
||||
const deprecations = [(settings) => {
|
||||
settings.origial = false;
|
||||
}];
|
||||
createTransform(deprecations)(settings);
|
||||
expect(settings.original).to.be(true);
|
||||
});
|
||||
|
||||
it('calls single deprecation in array', function () {
|
||||
const deprecations = [sinon.spy()];
|
||||
createTransform(deprecations)({});
|
||||
expect(deprecations[0].calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it('calls multiple deprecations in array', function () {
|
||||
const deprecations = [sinon.spy(), sinon.spy()];
|
||||
createTransform(deprecations)({});
|
||||
expect(deprecations[0].calledOnce).to.be(true);
|
||||
expect(deprecations[1].calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it('passes log function to deprecation', function () {
|
||||
const deprecation = sinon.spy();
|
||||
const log = function () {};
|
||||
createTransform([deprecation])({}, log);
|
||||
expect(deprecation.args[0][1]).to.be(log);
|
||||
});
|
||||
});
|
||||
});
|
14
src/deprecation/create_transform.js
Normal file
14
src/deprecation/create_transform.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { deepCloneWithBuffers as clone } from '../utils';
|
||||
import { forEach, noop } from 'lodash';
|
||||
|
||||
export default function (deprecations) {
|
||||
return (settings, log = noop) => {
|
||||
const result = clone(settings);
|
||||
|
||||
forEach(deprecations, (deprecation) => {
|
||||
deprecation(result, log);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
65
src/deprecation/deprecations/__tests__/rename.js
Normal file
65
src/deprecation/deprecations/__tests__/rename.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import expect from 'expect.js';
|
||||
import { noop } from 'lodash';
|
||||
import rename from '../rename';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('deprecation/deprecations', function () {
|
||||
describe('rename', function () {
|
||||
it('should rename simple property', function () {
|
||||
const value = 'value';
|
||||
const settings = {
|
||||
before: value
|
||||
};
|
||||
|
||||
rename('before', 'after')(settings);
|
||||
expect(settings.before).to.be(undefined);
|
||||
expect(settings.after).to.be(value);
|
||||
});
|
||||
|
||||
it ('should rename nested property', function () {
|
||||
const value = 'value';
|
||||
const settings = {
|
||||
someObject: {
|
||||
before: value
|
||||
}
|
||||
};
|
||||
|
||||
rename('someObject.before', 'someObject.after')(settings);
|
||||
expect(settings.someObject.before).to.be(undefined);
|
||||
expect(settings.someObject.after).to.be(value);
|
||||
});
|
||||
|
||||
it ('should rename property, even when the value is null', function () {
|
||||
const value = null;
|
||||
const settings = {
|
||||
before: value
|
||||
};
|
||||
|
||||
rename('before', 'after')(settings);
|
||||
expect(settings.before).to.be(undefined);
|
||||
expect(settings.after).to.be(null);
|
||||
});
|
||||
|
||||
it (`shouldn't log when a rename doesn't occur`, function () {
|
||||
const settings = {
|
||||
exists: true
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
rename('doesntExist', 'alsoDoesntExist')(settings, log);
|
||||
expect(log.called).to.be(false);
|
||||
});
|
||||
|
||||
it ('should log when a rename does occur', function () {
|
||||
const settings = {
|
||||
exists: true
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
rename('exists', 'alsoExists')(settings, log);
|
||||
|
||||
expect(log.calledOnce).to.be(true);
|
||||
expect(log.args[0][0]).to.match(/exists.+deprecated/);
|
||||
});
|
||||
});
|
||||
});
|
57
src/deprecation/deprecations/__tests__/unused.js
Normal file
57
src/deprecation/deprecations/__tests__/unused.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import unused from '../unused';
|
||||
|
||||
describe('deprecation/deprecations', function () {
|
||||
describe('unused', function () {
|
||||
it('should remove unused setting', function () {
|
||||
const settings = {
|
||||
old: true
|
||||
};
|
||||
|
||||
unused('old')(settings);
|
||||
expect(settings.old).to.be(undefined);
|
||||
});
|
||||
|
||||
it(`shouldn't remove used setting`, function () {
|
||||
const value = 'value';
|
||||
const settings = {
|
||||
new: value
|
||||
};
|
||||
|
||||
unused('old')(settings);
|
||||
expect(settings.new).to.be(value);
|
||||
});
|
||||
|
||||
it('should remove unused setting, even when null', function () {
|
||||
const settings = {
|
||||
old: null
|
||||
};
|
||||
|
||||
unused('old')(settings);
|
||||
expect(settings.old).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should log when removing unused setting', function () {
|
||||
const settings = {
|
||||
old: true
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
unused('old')(settings, log);
|
||||
|
||||
expect(log.calledOnce).to.be(true);
|
||||
expect(log.args[0][0]).to.match(/old.+deprecated/);
|
||||
});
|
||||
|
||||
it(`shouldn't log when no setting is unused`, function () {
|
||||
const settings = {
|
||||
new: true
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
unused('old')(settings, log);
|
||||
expect(log.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
2
src/deprecation/deprecations/index.js
Normal file
2
src/deprecation/deprecations/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export rename from './rename';
|
||||
export unused from './unused';
|
16
src/deprecation/deprecations/rename.js
Normal file
16
src/deprecation/deprecations/rename.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { get, isUndefined, isNull, noop, set } from 'lodash';
|
||||
import { unset } from '../../utils';
|
||||
|
||||
export default function (oldKey, newKey) {
|
||||
return (settings, log = noop) => {
|
||||
const value = get(settings, oldKey);
|
||||
if (isUndefined(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset(settings, oldKey);
|
||||
set(settings, newKey, value);
|
||||
|
||||
log(`Config key "${oldKey}" is deprecated. It has been replaced with "${newKey}"`);
|
||||
};
|
||||
}
|
14
src/deprecation/deprecations/unused.js
Normal file
14
src/deprecation/deprecations/unused.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { get, isUndefined, isNull, noop } from 'lodash';
|
||||
import { unset } from '../../utils';
|
||||
|
||||
export default function (oldKey) {
|
||||
return (settings, log = noop) => {
|
||||
const value = get(settings, oldKey);
|
||||
if (isUndefined(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset(settings, oldKey);
|
||||
log(`${oldKey} is deprecated and is no longer used`);
|
||||
};
|
||||
}
|
2
src/deprecation/index.js
Normal file
2
src/deprecation/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export createTransform from './create_transform';
|
||||
export * as Deprecations from './deprecations';
|
99
src/server/config/__tests__/complete.js
Normal file
99
src/server/config/__tests__/complete.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
import complete from '../complete';
|
||||
import expect from 'expect.js';
|
||||
import { noop } from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('server config complete', function () {
|
||||
it(`should call server.log when there's an unused setting`, function () {
|
||||
const kbnServer = {
|
||||
settings: {
|
||||
unused: true
|
||||
}
|
||||
};
|
||||
|
||||
const server = {
|
||||
decorate: noop,
|
||||
log: sinon.spy()
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: sinon.stub().returns({
|
||||
used: true
|
||||
})
|
||||
};
|
||||
|
||||
complete(kbnServer, server, config);
|
||||
|
||||
expect(server.log.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it(`shouldn't call server.log when there isn't an unused setting`, function () {
|
||||
const kbnServer = {
|
||||
settings: {
|
||||
used: true
|
||||
}
|
||||
};
|
||||
|
||||
const server = {
|
||||
decorate: noop,
|
||||
log: sinon.spy()
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: sinon.stub().returns({
|
||||
used: true
|
||||
})
|
||||
};
|
||||
|
||||
complete(kbnServer, server, config);
|
||||
|
||||
expect(server.log.called).to.be(false);
|
||||
});
|
||||
|
||||
it(`shouldn't call server.log when there are more config values than settings`, function () {
|
||||
const kbnServer = {
|
||||
settings: {
|
||||
used: true
|
||||
}
|
||||
};
|
||||
|
||||
const server = {
|
||||
decorate: noop,
|
||||
log: sinon.spy()
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: sinon.stub().returns({
|
||||
used: true,
|
||||
foo: 'bar'
|
||||
})
|
||||
};
|
||||
|
||||
complete(kbnServer, server, config);
|
||||
expect(server.log.called).to.be(false);
|
||||
});
|
||||
|
||||
it('should transform deprecated settings ', function () {
|
||||
const kbnServer = {
|
||||
settings: {
|
||||
port: 8080
|
||||
}
|
||||
};
|
||||
|
||||
const server = {
|
||||
decorate: noop,
|
||||
log: sinon.spy()
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: sinon.stub().returns({
|
||||
server: {
|
||||
port: 8080
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
complete(kbnServer, server, config);
|
||||
expect(server.log.called).to.be(false);
|
||||
});
|
||||
});
|
|
@ -217,13 +217,13 @@ describe('lib/config/config', function () {
|
|||
|
||||
it('should allow you to extend the schema at the top level', function () {
|
||||
const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
config.extendSchema('myTest', newSchema);
|
||||
config.extendSchema(newSchema, {}, 'myTest');
|
||||
expect(config.get('myTest.test')).to.be(true);
|
||||
});
|
||||
|
||||
it('should allow you to extend the schema with a prefix', function () {
|
||||
const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
config.extendSchema('prefix.myTest', newSchema);
|
||||
config.extendSchema(newSchema, {}, 'prefix.myTest');
|
||||
expect(config.get('prefix')).to.eql({ myTest: { test: true } });
|
||||
expect(config.get('prefix.myTest')).to.eql({ test: true });
|
||||
expect(config.get('prefix.myTest.test')).to.be(true);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import schemaProvider from '../schema';
|
||||
import expect from 'expect.js';
|
||||
import Joi from 'joi';
|
||||
import { set } from 'lodash';
|
||||
|
||||
describe('Config schema', function () {
|
||||
let schema;
|
||||
|
@ -11,6 +12,11 @@ describe('Config schema', function () {
|
|||
}
|
||||
|
||||
describe('server', function () {
|
||||
it('everything is optional', function () {
|
||||
const { error } = validate({});
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
|
||||
describe('basePath', function () {
|
||||
it('accepts empty strings', function () {
|
||||
const { error } = validate({ server: { basePath: '' } });
|
||||
|
@ -33,6 +39,163 @@ describe('Config schema', function () {
|
|||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.basePath');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('ssl', function () {
|
||||
describe('enabled', function () {
|
||||
|
||||
it('can\'t be a string', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', 'bogus');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.enabled');
|
||||
});
|
||||
|
||||
it('can be true', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', true);
|
||||
set(config, 'server.ssl.certificate', '/path.cert');
|
||||
set(config, 'server.ssl.key', '/path.key');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
|
||||
it('can be false', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', false);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('certificate', function () {
|
||||
|
||||
it('isn\'t required when ssl isn\'t enabled', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', false);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
|
||||
it('is required when ssl is enabled', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', true);
|
||||
set(config, 'server.ssl.key', '/path.key');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.certificate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('key', function () {
|
||||
it('isn\'t required when ssl isn\'t enabled', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', false);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
|
||||
it('is required when ssl is enabled', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.enabled', true);
|
||||
set(config, 'server.ssl.certificate', '/path.cert');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyPassphrase', function () {
|
||||
it ('is a possible config value', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.keyPassphrase', 'password');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clientAuthentication', function () {
|
||||
it ('defaults to \'none\'', function () {
|
||||
const config = {};
|
||||
const { error, value } = validate({});
|
||||
expect(error).to.be(null);
|
||||
expect(value).to.be.an(Object);
|
||||
expect(value.server).to.be.an(Object);
|
||||
expect(value.server.ssl).to.be.an(Object);
|
||||
expect(value.server.ssl.clientAuthentication).to.be('none');
|
||||
});
|
||||
|
||||
['none', 'required'].forEach((option) => {
|
||||
it(`allows ${option}`, function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.clientAuthentication', option);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
['bogus', 'somethingelse'].forEach((option) => {
|
||||
it(`rejects ${option}`, function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.clientAuthentication', option);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.clientAuthentication');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('certificateAuthorities', function () {
|
||||
it('allows array of string', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.certificateAuthorities', ['/path1.crt', '/path2.crt']);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
|
||||
it('allows a single string', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.certificateAuthorities', '/path1.crt');
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supportedProtocols', function () {
|
||||
|
||||
it ('rejects SSLv2', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.supportedProtocols', ['SSLv2']);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.supportedProtocols.0');
|
||||
});
|
||||
|
||||
it('rejects SSLv3', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.supportedProtocols', ['SSLv3']);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be.an(Object);
|
||||
expect(error).to.have.property('details');
|
||||
expect(error.details[0]).to.have.property('path', 'server.ssl.supportedProtocols.0');
|
||||
});
|
||||
|
||||
it('accepts TLSv1, TLSv1.1, TLSv1.2', function () {
|
||||
const config = {};
|
||||
set(config, 'server.ssl.supportedProtocols', ['TLSv1', 'TLSv1.1', 'TLSv1.2']);
|
||||
const { error } = validate(config);
|
||||
expect(error).to.be(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
61
src/server/config/__tests__/transform_deprecations.js
Normal file
61
src/server/config/__tests__/transform_deprecations.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { transformDeprecations } from '../transform_deprecations';
|
||||
|
||||
describe('server/config', function () {
|
||||
describe('transformDeprecations', function () {
|
||||
describe('server.ssl.enabled', function () {
|
||||
it('sets enabled to true when certificate and key are set', function () {
|
||||
const settings = {
|
||||
server: {
|
||||
ssl: {
|
||||
certificate: '/cert.crt',
|
||||
key: '/key.key'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result = transformDeprecations(settings);
|
||||
expect(result.server.ssl.enabled).to.be(true);
|
||||
});
|
||||
|
||||
it('logs a message when automatically setting enabled to true', function () {
|
||||
const settings = {
|
||||
server: {
|
||||
ssl: {
|
||||
certificate: '/cert.crt',
|
||||
key: '/key.key'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
it(`doesn't set enabled when key and cert aren't set`, function () {
|
||||
const settings = {
|
||||
server: {
|
||||
ssl: {}
|
||||
}
|
||||
};
|
||||
|
||||
const result = transformDeprecations(settings);
|
||||
expect(result.server.ssl.enabled).to.be(undefined);
|
||||
});
|
||||
|
||||
it(`doesn't log a message when not automatically setting enabled`, function () {
|
||||
const settings = {
|
||||
server: {
|
||||
ssl: {}
|
||||
}
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,11 +1,17 @@
|
|||
import { difference, keys } from 'lodash';
|
||||
import { transformDeprecations } from './transform_deprecations';
|
||||
|
||||
const getUnusedSettings = (settings, configValues) => {
|
||||
return difference(keys(transformDeprecations(settings)), keys(configValues));
|
||||
};
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
|
||||
server.decorate('server', 'config', function () {
|
||||
return kbnServer.config;
|
||||
});
|
||||
|
||||
const tmpl = 'Settings for "<%= key %>" were not applied, check for spelling errors and ensure the plugin is loaded.';
|
||||
for (const [key, val] of config.getPendingSets()) {
|
||||
server.log(['warning', 'config'], { key, val, tmpl });
|
||||
for (const key of getUnusedSettings(kbnServer.settings, config.get())) {
|
||||
server.log(['warning', 'config'], `Settings for "${key}" were not applied, check for spelling errors and ensure the plugin is loaded.`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import Joi from 'joi';
|
||||
import _ from 'lodash';
|
||||
import override from './override';
|
||||
import unset from './unset';
|
||||
import createDefaultSchema from './schema';
|
||||
import pkg from '../../utils/package_json';
|
||||
import clone from './deep_clone_with_buffers';
|
||||
import { pkg, unset } from '../../utils';
|
||||
import { deepCloneWithBuffers as clone } from '../../utils';
|
||||
|
||||
const schema = Symbol('Joi Schema');
|
||||
const schemaExts = Symbol('Schema Extensions');
|
||||
const vals = Symbol('config values');
|
||||
const pendingSets = Symbol('Pending Settings');
|
||||
|
||||
module.exports = class Config {
|
||||
static withDefaultSchema(settings = {}) {
|
||||
|
@ -19,19 +17,18 @@ module.exports = class Config {
|
|||
constructor(initialSchema, initialSettings) {
|
||||
this[schemaExts] = Object.create(null);
|
||||
this[vals] = Object.create(null);
|
||||
this[pendingSets] = _.merge(Object.create(null), initialSettings || {});
|
||||
|
||||
if (initialSchema) this.extendSchema(initialSchema);
|
||||
this.extendSchema(initialSchema, initialSettings);
|
||||
}
|
||||
|
||||
getPendingSets() {
|
||||
return new Map(_.pairs(this[pendingSets]));
|
||||
}
|
||||
extendSchema(extension, settings, key) {
|
||||
if (!extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
extendSchema(key, extension) {
|
||||
if (key && key.isJoi) {
|
||||
return _.each(key._inner.children, (child) => {
|
||||
this.extendSchema(child.key, child.schema);
|
||||
if (!key) {
|
||||
return _.each(extension._inner.children, (child) => {
|
||||
this.extendSchema(child.schema, _.get(settings, child.key), child.key);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,13 +39,7 @@ module.exports = class Config {
|
|||
_.set(this[schemaExts], key, extension);
|
||||
this[schema] = null;
|
||||
|
||||
const initialVals = _.get(this[pendingSets], key);
|
||||
if (initialVals) {
|
||||
this.set(key, initialVals);
|
||||
unset(this[pendingSets], key);
|
||||
} else {
|
||||
this._commit(this[vals]);
|
||||
}
|
||||
this.set(key, settings);
|
||||
}
|
||||
|
||||
removeSchema(key) {
|
||||
|
@ -58,7 +49,6 @@ module.exports = class Config {
|
|||
|
||||
this[schema] = null;
|
||||
unset(this[schemaExts], key);
|
||||
unset(this[pendingSets], key);
|
||||
unset(this[vals], key);
|
||||
}
|
||||
|
||||
|
|
7
src/server/config/deprecation_warnings.js
Normal file
7
src/server/config/deprecation_warnings.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { transformDeprecations } from './transform_deprecations';
|
||||
|
||||
export default function (kbnServer, server) {
|
||||
transformDeprecations(kbnServer.settings, (message) => {
|
||||
server.log(['warning', 'config', 'deprecation'], message);
|
||||
});
|
||||
}
|
|
@ -41,8 +41,19 @@ module.exports = () => Joi.object({
|
|||
defaultRoute: Joi.string().default('/app/kibana').regex(/^\//, `start with a slash`),
|
||||
basePath: Joi.string().default('').allow('').regex(/(^$|^\/.*[^\/]$)/, `start with a slash, don't end with one`),
|
||||
ssl: Joi.object({
|
||||
cert: Joi.string(),
|
||||
key: Joi.string()
|
||||
enabled: Joi.boolean().default(false),
|
||||
certificate: Joi.string().when('enabled', {
|
||||
is: true,
|
||||
then: Joi.required(),
|
||||
}),
|
||||
key: Joi.string().when('enabled', {
|
||||
is: true,
|
||||
then: Joi.required()
|
||||
}),
|
||||
keyPassphrase: Joi.string(),
|
||||
certificateAuthorities: Joi.array().single().items(Joi.string()),
|
||||
clientAuthentication: Joi.string().valid('none', 'required').default('none'),
|
||||
supportedProtocols: Joi.array().items(Joi.string().valid('TLSv1', 'TLSv1.1', 'TLSv1.2'))
|
||||
}).default(),
|
||||
cors: Joi.when('$dev', {
|
||||
is: true,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import Config from './config';
|
||||
import { transformDeprecations } from './transform_deprecations';
|
||||
|
||||
module.exports = function (kbnServer) {
|
||||
kbnServer.config = Config.withDefaultSchema(kbnServer.settings);
|
||||
const settings = transformDeprecations(kbnServer.settings);
|
||||
kbnServer.config = Config.withDefaultSchema(settings);
|
||||
};
|
||||
|
|
56
src/server/config/transform_deprecations.js
Normal file
56
src/server/config/transform_deprecations.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import _ , { partial } from 'lodash';
|
||||
import { createTransform, Deprecations } from '../../deprecation';
|
||||
|
||||
const { rename, unused } = Deprecations;
|
||||
|
||||
const serverSslEnabled = (settings, log) => {
|
||||
const has = partial(_.has, settings);
|
||||
const set = partial(_.set, settings);
|
||||
|
||||
if (!has('server.ssl.enabled') && has('server.ssl.certificate') && has('server.ssl.key')) {
|
||||
set('server.ssl.enabled', true);
|
||||
log('Enabling ssl by only specifying server.ssl.certificate and server.ssl.key is deprecated. Please set server.ssl.enabled to true');
|
||||
}
|
||||
};
|
||||
|
||||
const deprecations = [
|
||||
//server
|
||||
rename('port' ,'server.port'),
|
||||
rename('host', 'server.host'),
|
||||
rename('pid_file', 'pid.file'),
|
||||
rename('ssl_cert_file', 'server.ssl.certificate'),
|
||||
rename('server.ssl.cert', 'server.ssl.certificate'),
|
||||
rename('ssl_key_file', 'server.ssl.key'),
|
||||
unused('server.xsrf.token'),
|
||||
serverSslEnabled,
|
||||
|
||||
// logging
|
||||
rename('log_file', 'logging.dest'),
|
||||
|
||||
// kibana
|
||||
rename('kibana_index', 'kibana.index'),
|
||||
rename('default_app_id', 'kibana.defaultAppId'),
|
||||
|
||||
// es
|
||||
rename('ca', 'elasticsearch.ssl.ca'),
|
||||
rename('elasticsearch_preserve_host', 'elasticsearch.preserveHost'),
|
||||
rename('elasticsearch_url', 'elasticsearch.url'),
|
||||
rename('kibana_elasticsearch_client_crt', 'elasticsearch.ssl.cert'),
|
||||
rename('kibana_elasticsearch_client_key', 'elasticsearch.ssl.key'),
|
||||
rename('kibana_elasticsearch_password', 'elasticsearch.password'),
|
||||
rename('kibana_elasticsearch_username', 'elasticsearch.username'),
|
||||
rename('ping_timeout', 'elasticsearch.pingTimeout'),
|
||||
rename('request_timeout', 'elasticsearch.requestTimeout'),
|
||||
rename('shard_timeout', 'elasticsearch.shardTimeout'),
|
||||
rename('startup_timeout', 'elasticsearch.startupTimeout'),
|
||||
rename('verify_ssl', 'elasticsearch.ssl.verify'),
|
||||
|
||||
// tilemap
|
||||
rename('tilemap_url', 'tilemap.url'),
|
||||
rename('tilemap_min_zoom', 'tilemap.options.minZoom'),
|
||||
rename('tilemap_max_zoom', 'tilemap.options.maxZoom'),
|
||||
rename('tilemap_attribution', 'tilemap.options.attribution'),
|
||||
rename('tilemap_subdomains', 'tilemap.options.subdomains')
|
||||
];
|
||||
|
||||
export const transformDeprecations = createTransform(deprecations);
|
28
src/server/http/__tests__/secure_options.js
Normal file
28
src/server/http/__tests__/secure_options.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import expect from 'expect.js';
|
||||
import secureOptions from '../secure_options';
|
||||
import crypto from 'crypto';
|
||||
|
||||
const constants = crypto.constants;
|
||||
|
||||
describe('secure_options', function () {
|
||||
it('allows null', function () {
|
||||
expect(secureOptions(null)).to.be(null);
|
||||
});
|
||||
|
||||
it ('allows an empty array', function () {
|
||||
expect(secureOptions([])).to.be(null);
|
||||
});
|
||||
|
||||
it ('removes TLSv1 if we only support TLSv1.1 and TLSv1.2', function () {
|
||||
expect(secureOptions(['TLSv1.1', 'TLSv1.2'])).to.be(constants.SSL_OP_NO_TLSv1);
|
||||
});
|
||||
|
||||
it ('removes TLSv1.1 and TLSv1.2 if we only support TLSv1', function () {
|
||||
expect(secureOptions(['TLSv1'])).to.be(constants.SSL_OP_NO_TLSv1_1 | constants.SSL_OP_NO_TLSv1_2);
|
||||
});
|
||||
|
||||
it ('removes TLSv1 and TLSv1.1 if we only support TLSv1.2', function () {
|
||||
expect(secureOptions(['TLSv1.2'])).to.be(constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1);
|
||||
});
|
||||
|
||||
});
|
24
src/server/http/secure_options.js
Normal file
24
src/server/http/secure_options.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import crypto from 'crypto';
|
||||
import { chain } from 'lodash';
|
||||
|
||||
const constants = crypto.constants;
|
||||
|
||||
const protocolMap = {
|
||||
TLSv1: crypto.constants.SSL_OP_NO_TLSv1,
|
||||
'TLSv1.1': crypto.constants.SSL_OP_NO_TLSv1_1,
|
||||
'TLSv1.2': crypto.constants.SSL_OP_NO_TLSv1_2
|
||||
};
|
||||
|
||||
export default function (supportedProtocols) {
|
||||
if (!supportedProtocols || !supportedProtocols.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return chain(protocolMap)
|
||||
.omit(supportedProtocols)
|
||||
.values()
|
||||
.reduce(function (value, sum) {
|
||||
return value | sum;
|
||||
}, 0)
|
||||
.value();
|
||||
}
|
|
@ -1,9 +1,27 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { format as formatUrl } from 'url';
|
||||
import httpolyglot from 'httpolyglot';
|
||||
|
||||
import { map } from 'lodash';
|
||||
import secureOptions from './secure_options';
|
||||
import tlsCiphers from './tls_ciphers';
|
||||
|
||||
const getClientAuthenticationHttpOptions = (clientAuthentication) => {
|
||||
switch (clientAuthentication) {
|
||||
case 'none':
|
||||
return {
|
||||
requestCert: false,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
case 'required':
|
||||
return {
|
||||
requestCert: true,
|
||||
rejectUnauthorized: true
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unknown clientAuthentication option: ${clientAuthentication}`);
|
||||
}
|
||||
};
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
// this mixin is used outside of the kbn server, so it MUST work without a full kbnServer object.
|
||||
kbnServer = null;
|
||||
|
@ -25,8 +43,7 @@ export default function (kbnServer, server, config) {
|
|||
}
|
||||
};
|
||||
|
||||
// enable tlsOpts if ssl key and cert are defined
|
||||
const useSsl = config.get('server.ssl.key') && config.get('server.ssl.cert');
|
||||
const useSsl = config.get('server.ssl.enabled');
|
||||
|
||||
// not using https? well that's easy!
|
||||
if (!useSsl) {
|
||||
|
@ -34,16 +51,23 @@ export default function (kbnServer, server, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
const { requestCert, rejectUnauthorized } = getClientAuthenticationHttpOptions(config.get('server.ssl.clientAuthentication'));
|
||||
|
||||
server.connection({
|
||||
...connectionOptions,
|
||||
tls: true,
|
||||
listener: httpolyglot.createServer({
|
||||
key: readFileSync(config.get('server.ssl.key')),
|
||||
cert: readFileSync(config.get('server.ssl.cert')),
|
||||
cert: readFileSync(config.get('server.ssl.certificate')),
|
||||
ca: map(config.get('server.ssl.certificateAuthorities'), readFileSync),
|
||||
passphrase: config.get('server.ssl.keyPassphrase'),
|
||||
|
||||
ciphers: tlsCiphers,
|
||||
// We use the server's cipher order rather than the client's to prevent the BEAST attack
|
||||
honorCipherOrder: true
|
||||
honorCipherOrder: true,
|
||||
requestCert,
|
||||
rejectUnauthorized,
|
||||
secureOptions: secureOptions(config.get('server.ssl.supportedProtocols'))
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ module.exports = class KbnServer {
|
|||
require('./logging'),
|
||||
require('./warnings'),
|
||||
require('./status'),
|
||||
require('./config/deprecation_warnings'),
|
||||
|
||||
// writes pid file
|
||||
require('./pid'),
|
||||
|
|
|
@ -3,6 +3,7 @@ import Joi from 'joi';
|
|||
import Bluebird, { attempt, fromNode } from 'bluebird';
|
||||
import { basename, resolve } from 'path';
|
||||
import { inherits } from 'util';
|
||||
import { Deprecations } from '../../deprecation';
|
||||
|
||||
const extendInitFns = Symbol('extend plugin initialization');
|
||||
|
||||
|
@ -68,6 +69,7 @@ module.exports = class Plugin {
|
|||
this.externalInit = opts.init || _.noop;
|
||||
this.configPrefix = opts.configPrefix || this.id;
|
||||
this.getExternalConfigSchema = opts.config || _.noop;
|
||||
this.getExternalDeprecations = opts.deprecations || _.noop;
|
||||
this.preInit = _.once(this.preInit);
|
||||
this.init = _.once(this.init);
|
||||
this[extendInitFns] = [];
|
||||
|
@ -99,6 +101,11 @@ module.exports = class Plugin {
|
|||
return schema || defaultConfigSchema;
|
||||
}
|
||||
|
||||
getDeprecations() {
|
||||
const rules = this.getExternalDeprecations(Deprecations);
|
||||
return rules || [];
|
||||
}
|
||||
|
||||
async preInit() {
|
||||
return await this.externalPreInit(this.kbnServer.server);
|
||||
}
|
||||
|
|
|
@ -4,14 +4,24 @@ import { inspect } from 'util';
|
|||
import { get, indexBy } from 'lodash';
|
||||
import toPath from 'lodash/internal/toPath';
|
||||
import Collection from '../../utils/collection';
|
||||
import { transformDeprecations } from '../config/transform_deprecations';
|
||||
import { createTransform } from '../../deprecation';
|
||||
|
||||
const byIdCache = Symbol('byIdCache');
|
||||
const pluginApis = Symbol('pluginApis');
|
||||
|
||||
async function addPluginConfig(pluginCollection, plugin) {
|
||||
const { config, server, settings } = pluginCollection.kbnServer;
|
||||
|
||||
const transformedSettings = transformDeprecations(settings);
|
||||
const pluginSettings = get(transformedSettings, plugin.configPrefix);
|
||||
const deprecations = plugin.getDeprecations();
|
||||
const transformedPluginSettings = createTransform(deprecations)(pluginSettings, (message) => {
|
||||
server.log(['warning', plugin.configPrefix, 'config', 'deprecation'], message);
|
||||
});
|
||||
|
||||
const configSchema = await plugin.getConfigSchema();
|
||||
const { config } = pluginCollection.kbnServer;
|
||||
config.extendSchema(plugin.configPrefix, configSchema);
|
||||
config.extendSchema(configSchema, transformedPluginSettings, plugin.configPrefix);
|
||||
}
|
||||
|
||||
function removePluginConfig(pluginCollection, plugin) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export Binder from './binder';
|
||||
export BinderFor from './binder_for';
|
||||
export deepCloneWithBuffers from './deep_clone_with_buffers';
|
||||
export fromRoot from './from_root';
|
||||
export pkg from './package_json';
|
||||
export unset from './unset';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue