Modifying SSL settings to be consistent with the stack (#9823)
This introduces the following new settings: - server.ssl.enabled - server.ssl.keyPassphrase - server.ssl.certificateAuthorities - server.ssl.clientAuthentication - server.ssl.supportedProtocols - elasticsearch.ssl.keyPassphrase and deprecates the following: - server.ssl.cert -> server.ssl.certificate - elasticsearch.ssl.ca -> elasticsearch.ssl.certificateAuthorities - elasticsearch.ssl.cert -> elasticsearch.ssl.certificate - elasticsearch.ssl.verify -> elasticsearch.ssl.verificationMode - console.proxyConfig
This commit is contained in:
parent
1df8e80bc2
commit
5a4263835d
|
@ -39,22 +39,23 @@
|
||||||
#elasticsearch.username: "user"
|
#elasticsearch.username: "user"
|
||||||
#elasticsearch.password: "pass"
|
#elasticsearch.password: "pass"
|
||||||
|
|
||||||
# Paths to the PEM-format SSL certificate and SSL key files, respectively. These
|
# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
|
||||||
# files enable SSL for outgoing requests from the Kibana server to the browser.
|
# These settings enable SSL for outgoing requests from the Kibana server to the browser.
|
||||||
#server.ssl.cert: /path/to/your/server.crt
|
#server.ssl.enabled: false
|
||||||
|
#server.ssl.certificate: /path/to/your/server.crt
|
||||||
#server.ssl.key: /path/to/your/server.key
|
#server.ssl.key: /path/to/your/server.key
|
||||||
|
|
||||||
# Optional settings that provide the paths to the PEM-format SSL certificate and key files.
|
# 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.
|
# 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
|
#elasticsearch.ssl.key: /path/to/your/client.key
|
||||||
|
|
||||||
# Optional setting that enables you to specify a path to the PEM file for the certificate
|
# Optional setting that enables you to specify a path to the PEM file for the certificate
|
||||||
# authority for your Elasticsearch instance.
|
# 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.
|
# To disregard the validity of SSL certificates, change this setting's value to 'none'.
|
||||||
#elasticsearch.ssl.verify: true
|
#elasticsearch.ssl.verificationMode: full
|
||||||
|
|
||||||
# Time in milliseconds to wait for Elasticsearch to respond to pings. Defaults to the value of
|
# Time in milliseconds to wait for Elasticsearch to respond to pings. Defaults to the value of
|
||||||
# the elasticsearch.requestTimeout setting.
|
# the elasticsearch.requestTimeout setting.
|
||||||
|
|
|
@ -4,54 +4,3 @@
|
||||||
You can add the following options in the `config/kibana.yml` file:
|
You can add the following options in the `config/kibana.yml` file:
|
||||||
|
|
||||||
`console.enabled`:: *Default: true* Set to false to disable Console. Toggling this will cause the server to regenerate assets on the next startup, which may cause a delay before pages start being served.
|
`console.enabled`:: *Default: true* Set to false to disable Console. Toggling this will cause the server to regenerate assets on the next startup, which may cause a delay before pages start being served.
|
||||||
|
|
||||||
`console.proxyFilter`:: *Default: `.*`* A list of regular expressions that are used to validate any outgoing request from Console. If none
|
|
||||||
of these match, the request will be rejected. See <<securing-console>> for more details.
|
|
||||||
|
|
||||||
`console.proxyConfig`:: A list of configuration options that are based on the proxy target. Use this to set custom timeouts or SSL settings for specific hosts. This is done by defining a set of `match` criteria using wildcards/globs which will be checked against each request. The configuration from all matching rules will then be merged together to configure the proxy used for that request.
|
|
||||||
+
|
|
||||||
The valid match keys are `match.protocol`, `match.host`, `match.port`, and `match.path`. All of these keys default to `*`, which means they will match any value.
|
|
||||||
+
|
|
||||||
Example:
|
|
||||||
+
|
|
||||||
[source,yaml]
|
|
||||||
--------
|
|
||||||
console.proxyConfig:
|
|
||||||
- match:
|
|
||||||
host: "*.internal.org" # allow any host that ends in .internal.org
|
|
||||||
port: "{9200..9299}" # allow any port from 9200-9299
|
|
||||||
|
|
||||||
ssl:
|
|
||||||
ca: "/opt/certs/internal.ca"
|
|
||||||
# "key" and "cert" are also valid options here
|
|
||||||
|
|
||||||
- match:
|
|
||||||
protocol: "https"
|
|
||||||
|
|
||||||
ssl:
|
|
||||||
verify: false # allows any certificate to be used, even self-signed certs
|
|
||||||
|
|
||||||
# since this rule has no "match" section it matches everything
|
|
||||||
- timeout: 180000 # 3 minutes
|
|
||||||
--------
|
|
||||||
|
|
||||||
[[securing-console]]
|
|
||||||
=== Securing Console
|
|
||||||
|
|
||||||
Console is meant to be used as a local development tool. As such, it will send requests to any host & port combination,
|
|
||||||
just as a local curl command would. To overcome the CORS limitations enforced by browsers, Console's Node.js backend
|
|
||||||
serves as a proxy to send requests on behalf of the browser. However, if put on a server and exposed to the internet
|
|
||||||
this can become a security risk. In those cases, we highly recommend you lock down the proxy by setting the
|
|
||||||
`console.proxyFilter` setting. The setting accepts a list of regular expressions that are evaluated against each URL
|
|
||||||
the proxy is requested to retrieve. If none of the regular expressions match the proxy will reject the request.
|
|
||||||
|
|
||||||
Here is an example configuration the only allows Console to connect to localhost:
|
|
||||||
|
|
||||||
[source,yaml]
|
|
||||||
--------
|
|
||||||
console.proxyFilter:
|
|
||||||
- ^https?://(localhost|127\.0\.0\.1|\[::0\]).*
|
|
||||||
--------
|
|
||||||
|
|
||||||
You will need to restart Kibana for these changes to take effect.
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
Kibana supports SSL encryption for both client requests and the requests the Kibana server
|
||||||
sends to Elasticsearch.
|
sends to Elasticsearch.
|
||||||
|
|
||||||
To encrypt communications between the browser and the Kibana server, you configure the `ssl_key_file` and
|
To encrypt communications between the browser and the Kibana server, you configure the `server.ssl.enabled`,
|
||||||
`ssl_cert_file` properties in `kibana.yml`:
|
`server.ssl.certificate` and `server.ssl.key` properties in `kibana.yml`:
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
----
|
----
|
||||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||||
|
server.ssl.enabled: true
|
||||||
server.ssl.key: /path/to/your/server.key
|
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,
|
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]
|
[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
|
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 `ca` property lets you leave the `verify_ssl` option enabled.
|
`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]
|
[source,text]
|
||||||
----
|
----
|
||||||
# If you need to provide a CA certificate for your Elasticsearch instance, put
|
# If you need to provide a CA certificate for your Elasticsearch instance, put
|
||||||
# the path of the pem file here.
|
# the path of the pem file here.
|
||||||
ca: /path/to/your/ca/cacert.pem
|
elasticsearch.ssl.certificateAuthorities: [ "/path/to/your/ca/cacert.pem" ]
|
||||||
----
|
----
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
|
|
|
@ -33,14 +33,21 @@ 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,
|
`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
|
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.
|
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
|
`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
|
||||||
files enable SSL for outgoing requests from the Kibana server to the browser.
|
`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`
|
||||||
|
`server.ssl.cipherSuites`:: *Default: ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-SHA384, DHE-RSA-AES256-SHA384, ECDHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA256, HIGH,!aNULL, !eNULL, !EXPORT, !DES, !RC4, !MD5, !PSK, !SRP, !CAMELLIA*. Details on the format, and the valid options, are available via the [OpenSSL cipher list format documentation](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT)
|
||||||
`elasticsearch.ssl.cert:` and `elasticsearch.ssl.key:`:: Optional settings that provide the paths to the PEM-format SSL
|
`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.
|
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.
|
authority for your Elasticsearch instance.
|
||||||
`elasticsearch.ssl.verify:`:: *Default: true* To disregard the validity of SSL certificates, change this setting’s value
|
`elasticsearch.ssl.verificationMode:`:: *Default: full* Controls the verification of certificates. Valid values are `none`, `certificate`, and `full`.
|
||||||
to `false`.
|
`full` performs hostname verification, and `certificate` does not.
|
||||||
`elasticsearch.pingTimeout:`:: *Default: the value of the `elasticsearch.requestTimeout` setting* Time in milliseconds to
|
`elasticsearch.pingTimeout:`:: *Default: the value of the `elasticsearch.requestTimeout` setting* Time in milliseconds to
|
||||||
wait for Elasticsearch to respond to pings.
|
wait for Elasticsearch to respond to pings.
|
||||||
`elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or
|
`elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or
|
||||||
|
@ -65,10 +72,6 @@ The minimum value is 100.
|
||||||
`status.allowAnonymous`:: *Default: false* If authentication is enabled, setting this to `true` allows
|
`status.allowAnonymous`:: *Default: false* If authentication is enabled, setting this to `true` allows
|
||||||
unauthenticated users to access the Kibana server status API and status page.
|
unauthenticated users to access the Kibana server status API and status page.
|
||||||
`console.enabled`:: *Default: true* Set to false to disable Console. Toggling this will cause the server to regenerate assets on the next startup, which may cause a delay before pages start being served.
|
`console.enabled`:: *Default: true* Set to false to disable Console. Toggling this will cause the server to regenerate assets on the next startup, which may cause a delay before pages start being served.
|
||||||
`console.proxyFilter`:: *Default: `.*`* A list of regular expressions that are used to validate any outgoing request from Console. If none of these match, the request will be rejected.
|
|
||||||
`console.proxyConfig`:: A list of configuration options that are based on the proxy target. Use this to set custom timeouts or SSL settings for specific hosts. This is done by defining a set of `match` criteria using wildcards/globs which will be checked against each request. The configuration from all matching rules will then be merged together to configure the proxy used for that request.
|
|
||||||
+
|
|
||||||
The valid match keys are `match.protocol`, `match.host`, `match.port`, and `match.path`. All of these keys default to `*`, which means they will match any value. See <<configuring-console>> for an example.
|
|
||||||
|
|
||||||
`elasticsearch.tribe.url:`:: Optional URL of the Elasticsearch tribe instance to use for all your
|
`elasticsearch.tribe.url:`:: Optional URL of the Elasticsearch tribe instance to use for all your
|
||||||
queries.
|
queries.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Server } from 'hapi';
|
import { Server } from 'hapi';
|
||||||
import { notFound } from 'boom';
|
import { notFound } from 'boom';
|
||||||
import { merge, sample } from 'lodash';
|
import { map, merge, sample } from 'lodash';
|
||||||
import { format as formatUrl } from 'url';
|
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 { Agent as HttpsAgent } from 'https';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import Config from '../../server/config/config';
|
||||||
import setupConnection from '../../server/http/setup_connection';
|
import setupConnection from '../../server/http/setup_connection';
|
||||||
import registerHapiPlugins from '../../server/http/register_hapi_plugins';
|
import registerHapiPlugins from '../../server/http/register_hapi_plugins';
|
||||||
import setupLogging from '../../server/logging';
|
import setupLogging from '../../server/logging';
|
||||||
|
import { transformDeprecations } from '../../server/config/transform_deprecations';
|
||||||
import { DEV_SSL_CERT_PATH } from '../dev_ssl';
|
import { DEV_SSL_CERT_PATH } from '../dev_ssl';
|
||||||
|
|
||||||
const alphabet = 'abcdefghijklmnopqrztuvwxyz'.split('');
|
const alphabet = 'abcdefghijklmnopqrztuvwxyz'.split('');
|
||||||
|
@ -19,20 +20,21 @@ export default class BasePathProxy {
|
||||||
this.clusterManager = clusterManager;
|
this.clusterManager = clusterManager;
|
||||||
this.server = new Server();
|
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.targetPort = config.get('dev.basePathProxyTarget');
|
||||||
this.basePath = config.get('server.basePath');
|
this.basePath = config.get('server.basePath');
|
||||||
|
|
||||||
const { cert } = config.get('server.ssl');
|
const sslEnabled = config.get('server.ssl.enabled');
|
||||||
if (cert) {
|
if (sslEnabled) {
|
||||||
const httpsAgentConfig = {};
|
this.proxyAgent = new HttpsAgent({
|
||||||
if (cert === DEV_SSL_CERT_PATH && config.get('server.host') !== 'localhost') {
|
key: readFileSync(config.get('server.ssl.key')),
|
||||||
httpsAgentConfig.rejectUnauthorized = false;
|
passphrase: config.get('server.ssl.keyPassphrase'),
|
||||||
} else {
|
cert: readFileSync(config.get('server.ssl.certificate')),
|
||||||
httpsAgentConfig.ca = readFileSync(cert);
|
ca: map(config.get('server.ssl.certificateAuthorities'), readFileSync),
|
||||||
}
|
rejectUnauthorized: false
|
||||||
this.proxyAgent = new HttpsAgent(httpsAgentConfig);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.basePath) {
|
if (!this.basePath) {
|
||||||
|
@ -67,7 +69,7 @@ export default class BasePathProxy {
|
||||||
config: {
|
config: {
|
||||||
pre: [
|
pre: [
|
||||||
(req, reply) => {
|
(req, reply) => {
|
||||||
map(clusterManager.workers, worker => {
|
promiseMap(clusterManager.workers, worker => {
|
||||||
if (worker.type === 'server' && !worker.listening && !worker.crashed) {
|
if (worker.type === 'server' && !worker.listening && !worker.crashed) {
|
||||||
return fromNode(cb => {
|
return fromNode(cb => {
|
||||||
const done = () => {
|
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);
|
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 { readFileSync as read } from 'fs';
|
||||||
import { safeLoad } from 'js-yaml';
|
import { safeLoad } from 'js-yaml';
|
||||||
import { red } from 'ansicolors';
|
|
||||||
|
|
||||||
import { fromRoot } from '../../utils';
|
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) {
|
export function merge(sources) {
|
||||||
return transform(sources, (merged, source) => {
|
return transform(sources, (merged, source) => {
|
||||||
|
@ -35,6 +29,5 @@ export function merge(sources) {
|
||||||
export default function (paths) {
|
export default function (paths) {
|
||||||
const files = [].concat(paths || []);
|
const files = [].concat(paths || []);
|
||||||
const yamls = files.map(path => safeLoad(read(path, 'utf8')));
|
const yamls = files.map(path => safeLoad(read(path, 'utf8')));
|
||||||
const config = merge(yamls.map(file => rewriteLegacyConfig(file, log)));
|
return merge(yamls);
|
||||||
return checkForDeprecatedConfig(config, log);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,13 @@ function readServerSettings(opts, extraCliOptions) {
|
||||||
if (opts.dev) {
|
if (opts.dev) {
|
||||||
set('env', 'development');
|
set('env', 'development');
|
||||||
set('optimize.lazy', true);
|
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);
|
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 apiServer from './api_server/server';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { resolve, join, sep } from 'path';
|
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 { ProxyConfigCollection } from './server/proxy_config_collection';
|
||||||
|
import { getElasticsearchProxyConfig } from './server/elasticsearch_proxy_config';
|
||||||
|
|
||||||
export default function (kibana) {
|
export default function (kibana) {
|
||||||
const modules = resolve(__dirname, 'public/webpackShims/');
|
const modules = resolve(__dirname, 'public/webpackShims/');
|
||||||
|
@ -50,24 +51,20 @@ export default function (kibana) {
|
||||||
key: Joi.string()
|
key: Joi.string()
|
||||||
}).default()
|
}).default()
|
||||||
})
|
})
|
||||||
).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) {
|
init: function (server, options) {
|
||||||
const filters = options.proxyFilter.map(str => new RegExp(str));
|
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 requestHeadersWhitelist = server.config().get('elasticsearch.requestHeadersWhitelist');
|
||||||
const filterHeaders = server.plugins.elasticsearch.filterHeaders;
|
const filterHeaders = server.plugins.elasticsearch.filterHeaders;
|
||||||
|
|
||||||
|
let additionalConfig;
|
||||||
|
if (server.config().get('console.proxyConfig')) {
|
||||||
|
additionalConfig = proxyConfigCollection.configForUri(uri);
|
||||||
|
} else {
|
||||||
|
additionalConfig = getElasticsearchProxyConfig(server);
|
||||||
|
}
|
||||||
|
|
||||||
reply.proxy({
|
reply.proxy({
|
||||||
mapUri: function (request, done) {
|
mapUri: function (request, done) {
|
||||||
done(null, uri, filterHeaders(request.headers, requestHeadersWhitelist));
|
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 { compact, get, has, set, trim, trimRight } from 'lodash';
|
||||||
|
import { unset } from '../../utils';
|
||||||
import { methodNotAllowed } from 'boom';
|
import { methodNotAllowed } from 'boom';
|
||||||
|
|
||||||
import healthCheck from './lib/health_check';
|
import healthCheck from './lib/health_check';
|
||||||
|
@ -19,6 +20,14 @@ module.exports = function ({ Plugin }) {
|
||||||
config(Joi) {
|
config(Joi) {
|
||||||
const { array, boolean, number, object, string, ref } = 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({
|
return object({
|
||||||
enabled: boolean().default(true),
|
enabled: boolean().default(true),
|
||||||
url: string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
url: string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||||
|
@ -32,12 +41,7 @@ module.exports = function ({ Plugin }) {
|
||||||
pingTimeout: number().default(ref('requestTimeout')),
|
pingTimeout: number().default(ref('requestTimeout')),
|
||||||
startupTimeout: number().default(5000),
|
startupTimeout: number().default(5000),
|
||||||
logQueries: boolean().default(false),
|
logQueries: boolean().default(false),
|
||||||
ssl: object({
|
ssl: sslSchema,
|
||||||
verify: boolean().default(true),
|
|
||||||
ca: array().single().items(string()),
|
|
||||||
cert: string(),
|
|
||||||
key: string()
|
|
||||||
}).default(),
|
|
||||||
apiVersion: Joi.string().default('master'),
|
apiVersion: Joi.string().default('master'),
|
||||||
healthCheck: object({
|
healthCheck: object({
|
||||||
delay: number().default(2500)
|
delay: number().default(2500)
|
||||||
|
@ -54,17 +58,43 @@ module.exports = function ({ Plugin }) {
|
||||||
pingTimeout: number().default(ref('requestTimeout')),
|
pingTimeout: number().default(ref('requestTimeout')),
|
||||||
startupTimeout: number().default(5000),
|
startupTimeout: number().default(5000),
|
||||||
logQueries: boolean().default(false),
|
logQueries: boolean().default(false),
|
||||||
ssl: object({
|
ssl: sslSchema,
|
||||||
verify: boolean().default(true),
|
|
||||||
ca: array().single().items(string()),
|
|
||||||
cert: string(),
|
|
||||||
key: string()
|
|
||||||
}).default(),
|
|
||||||
apiVersion: Joi.string().default('master'),
|
apiVersion: Joi.string().default('master'),
|
||||||
}).default()
|
}).default()
|
||||||
}).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: {
|
uiExports: {
|
||||||
injectDefaultVars(server, options) {
|
injectDefaultVars(server, options) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe('plugins/elasticsearch', function () {
|
||||||
let cluster;
|
let cluster;
|
||||||
const config = {
|
const config = {
|
||||||
url: 'http://localhost:9200',
|
url: 'http://localhost:9200',
|
||||||
ssl: { verify: false },
|
ssl: { verificationMode: 'full' },
|
||||||
requestHeadersWhitelist: [ 'authorization' ]
|
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 = {
|
const config = {
|
||||||
url: 'http://localhost:9200',
|
url: 'http://localhost:9200',
|
||||||
ssl: {
|
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 util from 'util';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import { get, size, pick } from 'lodash';
|
import { get, noop, size, pick } from 'lodash';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
@ -30,15 +30,35 @@ export function parseConfig(serverConfig = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSL
|
// SSL
|
||||||
config.ssl = { rejectUnauthorized: get(serverConfig, 'ssl.verify') };
|
config.ssl = {};
|
||||||
|
|
||||||
if (get(serverConfig, 'ssl.cert') && get(serverConfig, 'ssl.key')) {
|
const verificationMode = get(serverConfig, 'ssl.verificationMode');
|
||||||
config.ssl.cert = readFile(serverConfig.ssl.cert);
|
switch (verificationMode) {
|
||||||
config.ssl.key = readFile(serverConfig.ssl.key);
|
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'))) {
|
if (size(get(serverConfig, 'ssl.certificateAuthorities'))) {
|
||||||
config.ssl.ca = serverConfig.ssl.ca.map(readFile);
|
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();
|
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, 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, 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 () {
|
it('should allow you to extend the schema at the top level', function () {
|
||||||
const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
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);
|
expect(config.get('myTest.test')).to.be(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow you to extend the schema with a prefix', function () {
|
it('should allow you to extend the schema with a prefix', function () {
|
||||||
const newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
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')).to.eql({ myTest: { test: true } });
|
||||||
expect(config.get('prefix.myTest')).to.eql({ test: true });
|
expect(config.get('prefix.myTest')).to.eql({ test: true });
|
||||||
expect(config.get('prefix.myTest.test')).to.be(true);
|
expect(config.get('prefix.myTest.test')).to.be(true);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import schemaProvider from '../schema';
|
import schemaProvider from '../schema';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
|
import { set } from 'lodash';
|
||||||
|
|
||||||
describe('Config schema', function () {
|
describe('Config schema', function () {
|
||||||
let schema;
|
let schema;
|
||||||
|
@ -11,6 +12,11 @@ describe('Config schema', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('server', function () {
|
describe('server', function () {
|
||||||
|
it('everything is optional', function () {
|
||||||
|
const { error } = validate({});
|
||||||
|
expect(error).to.be(null);
|
||||||
|
});
|
||||||
|
|
||||||
describe('basePath', function () {
|
describe('basePath', function () {
|
||||||
it('accepts empty strings', function () {
|
it('accepts empty strings', function () {
|
||||||
const { error } = validate({ server: { basePath: '' } });
|
const { error } = validate({ server: { basePath: '' } });
|
||||||
|
@ -33,6 +39,163 @@ describe('Config schema', function () {
|
||||||
expect(error).to.have.property('details');
|
expect(error).to.have.property('details');
|
||||||
expect(error.details[0]).to.have.property('path', 'server.basePath');
|
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) {
|
export default function (kbnServer, server, config) {
|
||||||
|
|
||||||
server.decorate('server', 'config', function () {
|
server.decorate('server', 'config', function () {
|
||||||
return kbnServer.config;
|
return kbnServer.config;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tmpl = 'Settings for "<%= key %>" were not applied, check for spelling errors and ensure the plugin is loaded.';
|
for (const key of getUnusedSettings(kbnServer.settings, config.get())) {
|
||||||
for (const [key, val] of config.getPendingSets()) {
|
server.log(['warning', 'config'], `Settings for "${key}" were not applied, check for spelling errors and ensure the plugin is loaded.`);
|
||||||
server.log(['warning', 'config'], { key, val, tmpl });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import override from './override';
|
import override from './override';
|
||||||
import unset from './unset';
|
|
||||||
import createDefaultSchema from './schema';
|
import createDefaultSchema from './schema';
|
||||||
import pkg from '../../utils/package_json';
|
import { pkg, unset } from '../../utils';
|
||||||
import clone from './deep_clone_with_buffers';
|
import { deepCloneWithBuffers as clone } from '../../utils';
|
||||||
|
|
||||||
const schema = Symbol('Joi Schema');
|
const schema = Symbol('Joi Schema');
|
||||||
const schemaExts = Symbol('Schema Extensions');
|
const schemaExts = Symbol('Schema Extensions');
|
||||||
const vals = Symbol('config values');
|
const vals = Symbol('config values');
|
||||||
const pendingSets = Symbol('Pending Settings');
|
|
||||||
|
|
||||||
module.exports = class Config {
|
module.exports = class Config {
|
||||||
static withDefaultSchema(settings = {}) {
|
static withDefaultSchema(settings = {}) {
|
||||||
|
@ -19,19 +17,18 @@ module.exports = class Config {
|
||||||
constructor(initialSchema, initialSettings) {
|
constructor(initialSchema, initialSettings) {
|
||||||
this[schemaExts] = Object.create(null);
|
this[schemaExts] = Object.create(null);
|
||||||
this[vals] = 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() {
|
extendSchema(extension, settings, key) {
|
||||||
return new Map(_.pairs(this[pendingSets]));
|
if (!extension) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
extendSchema(key, extension) {
|
if (!key) {
|
||||||
if (key && key.isJoi) {
|
return _.each(extension._inner.children, (child) => {
|
||||||
return _.each(key._inner.children, (child) => {
|
this.extendSchema(child.schema, _.get(settings, child.key), child.key);
|
||||||
this.extendSchema(child.key, child.schema);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,13 +39,7 @@ module.exports = class Config {
|
||||||
_.set(this[schemaExts], key, extension);
|
_.set(this[schemaExts], key, extension);
|
||||||
this[schema] = null;
|
this[schema] = null;
|
||||||
|
|
||||||
const initialVals = _.get(this[pendingSets], key);
|
this.set(key, settings);
|
||||||
if (initialVals) {
|
|
||||||
this.set(key, initialVals);
|
|
||||||
unset(this[pendingSets], key);
|
|
||||||
} else {
|
|
||||||
this._commit(this[vals]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSchema(key) {
|
removeSchema(key) {
|
||||||
|
@ -58,7 +49,6 @@ module.exports = class Config {
|
||||||
|
|
||||||
this[schema] = null;
|
this[schema] = null;
|
||||||
unset(this[schemaExts], key);
|
unset(this[schemaExts], key);
|
||||||
unset(this[pendingSets], key);
|
|
||||||
unset(this[vals], 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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes, constants as cryptoConstants } from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
import { fromRoot } from '../../utils';
|
import { fromRoot } from '../../utils';
|
||||||
|
@ -41,8 +41,20 @@ module.exports = () => Joi.object({
|
||||||
defaultRoute: Joi.string().default('/app/kibana').regex(/^\//, `start with a slash`),
|
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`),
|
basePath: Joi.string().default('').allow('').regex(/(^$|^\/.*[^\/]$)/, `start with a slash, don't end with one`),
|
||||||
ssl: Joi.object({
|
ssl: Joi.object({
|
||||||
cert: Joi.string(),
|
enabled: Joi.boolean().default(false),
|
||||||
key: Joi.string()
|
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')),
|
||||||
|
cipherSuites: Joi.array().items(Joi.string()).default(cryptoConstants.defaultCoreCipherList.split(':'))
|
||||||
}).default(),
|
}).default(),
|
||||||
cors: Joi.when('$dev', {
|
cors: Joi.when('$dev', {
|
||||||
is: true,
|
is: true,
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import Config from './config';
|
import Config from './config';
|
||||||
|
import { transformDeprecations } from './transform_deprecations';
|
||||||
|
|
||||||
module.exports = function (kbnServer) {
|
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,8 +1,25 @@
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { format as formatUrl } from 'url';
|
import { format as formatUrl } from 'url';
|
||||||
import httpolyglot from 'httpolyglot';
|
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) {
|
export default function (kbnServer, server, config) {
|
||||||
// this mixin is used outside of the kbn server, so it MUST work without a full kbnServer object.
|
// this mixin is used outside of the kbn server, so it MUST work without a full kbnServer object.
|
||||||
|
@ -25,8 +42,7 @@ export default function (kbnServer, server, config) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// enable tlsOpts if ssl key and cert are defined
|
const useSsl = config.get('server.ssl.enabled');
|
||||||
const useSsl = config.get('server.ssl.key') && config.get('server.ssl.cert');
|
|
||||||
|
|
||||||
// not using https? well that's easy!
|
// not using https? well that's easy!
|
||||||
if (!useSsl) {
|
if (!useSsl) {
|
||||||
|
@ -34,16 +50,23 @@ export default function (kbnServer, server, config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { requestCert, rejectUnauthorized } = getClientAuthenticationHttpOptions(config.get('server.ssl.clientAuthentication'));
|
||||||
|
|
||||||
server.connection({
|
server.connection({
|
||||||
...connectionOptions,
|
...connectionOptions,
|
||||||
tls: true,
|
tls: true,
|
||||||
listener: httpolyglot.createServer({
|
listener: httpolyglot.createServer({
|
||||||
key: readFileSync(config.get('server.ssl.key')),
|
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,
|
ciphers: config.get('server.ssl.cipherSuites').join(':'),
|
||||||
// We use the server's cipher order rather than the client's to prevent the BEAST attack
|
// 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'))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// The default ciphers in node 0.12.x include insecure ciphers, so until
|
|
||||||
// we enforce a more recent version of node, we craft our own list
|
|
||||||
// @see https://github.com/nodejs/node/blob/master/src/node_constants.h#L8-L28
|
|
||||||
export default [
|
|
||||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
|
||||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
||||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
||||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
||||||
'DHE-RSA-AES128-GCM-SHA256',
|
|
||||||
'ECDHE-RSA-AES128-SHA256',
|
|
||||||
'DHE-RSA-AES128-SHA256',
|
|
||||||
'ECDHE-RSA-AES256-SHA384',
|
|
||||||
'DHE-RSA-AES256-SHA384',
|
|
||||||
'ECDHE-RSA-AES256-SHA256',
|
|
||||||
'DHE-RSA-AES256-SHA256',
|
|
||||||
'HIGH',
|
|
||||||
'!aNULL',
|
|
||||||
'!eNULL',
|
|
||||||
'!EXPORT',
|
|
||||||
'!DES',
|
|
||||||
'!RC4',
|
|
||||||
'!MD5',
|
|
||||||
'!PSK',
|
|
||||||
'!SRP',
|
|
||||||
'!CAMELLIA'
|
|
||||||
].join(':');
|
|
|
@ -19,6 +19,7 @@ module.exports = class KbnServer {
|
||||||
require('./config/setup'), // sets this.config, reads this.settings
|
require('./config/setup'), // sets this.config, reads this.settings
|
||||||
require('./http'), // sets this.server
|
require('./http'), // sets this.server
|
||||||
require('./logging'),
|
require('./logging'),
|
||||||
|
require('./config/deprecation_warnings'),
|
||||||
require('./warnings'),
|
require('./warnings'),
|
||||||
require('./status'),
|
require('./status'),
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Joi from 'joi';
|
||||||
import Bluebird, { attempt, fromNode } from 'bluebird';
|
import Bluebird, { attempt, fromNode } from 'bluebird';
|
||||||
import { basename, resolve } from 'path';
|
import { basename, resolve } from 'path';
|
||||||
import { inherits } from 'util';
|
import { inherits } from 'util';
|
||||||
|
import { Deprecations } from '../../deprecation';
|
||||||
|
|
||||||
const extendInitFns = Symbol('extend plugin initialization');
|
const extendInitFns = Symbol('extend plugin initialization');
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ module.exports = class Plugin {
|
||||||
this.externalInit = opts.init || _.noop;
|
this.externalInit = opts.init || _.noop;
|
||||||
this.configPrefix = opts.configPrefix || this.id;
|
this.configPrefix = opts.configPrefix || this.id;
|
||||||
this.getExternalConfigSchema = opts.config || _.noop;
|
this.getExternalConfigSchema = opts.config || _.noop;
|
||||||
|
this.getExternalDeprecations = opts.deprecations || _.noop;
|
||||||
this.preInit = _.once(this.preInit);
|
this.preInit = _.once(this.preInit);
|
||||||
this.init = _.once(this.init);
|
this.init = _.once(this.init);
|
||||||
this[extendInitFns] = [];
|
this[extendInitFns] = [];
|
||||||
|
@ -99,6 +101,11 @@ module.exports = class Plugin {
|
||||||
return schema || defaultConfigSchema;
|
return schema || defaultConfigSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDeprecations() {
|
||||||
|
const rules = this.getExternalDeprecations(Deprecations);
|
||||||
|
return rules || [];
|
||||||
|
}
|
||||||
|
|
||||||
async preInit() {
|
async preInit() {
|
||||||
return await this.externalPreInit(this.kbnServer.server);
|
return await this.externalPreInit(this.kbnServer.server);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,24 @@ import { inspect } from 'util';
|
||||||
import { get, indexBy } from 'lodash';
|
import { get, indexBy } from 'lodash';
|
||||||
import toPath from 'lodash/internal/toPath';
|
import toPath from 'lodash/internal/toPath';
|
||||||
import Collection from '../../utils/collection';
|
import Collection from '../../utils/collection';
|
||||||
|
import { transformDeprecations } from '../config/transform_deprecations';
|
||||||
|
import { createTransform } from '../../deprecation';
|
||||||
|
|
||||||
const byIdCache = Symbol('byIdCache');
|
const byIdCache = Symbol('byIdCache');
|
||||||
const pluginApis = Symbol('pluginApis');
|
const pluginApis = Symbol('pluginApis');
|
||||||
|
|
||||||
async function addPluginConfig(pluginCollection, plugin) {
|
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 configSchema = await plugin.getConfigSchema();
|
||||||
const { config } = pluginCollection.kbnServer;
|
config.extendSchema(configSchema, transformedPluginSettings, plugin.configPrefix);
|
||||||
config.extendSchema(plugin.configPrefix, configSchema);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePluginConfig(pluginCollection, plugin) {
|
function removePluginConfig(pluginCollection, plugin) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
export Binder from './binder';
|
export Binder from './binder';
|
||||||
export BinderFor from './binder_for';
|
export BinderFor from './binder_for';
|
||||||
|
export deepCloneWithBuffers from './deep_clone_with_buffers';
|
||||||
export fromRoot from './from_root';
|
export fromRoot from './from_root';
|
||||||
export pkg from './package_json';
|
export pkg from './package_json';
|
||||||
|
export unset from './unset';
|
||||||
|
|
Loading…
Reference in a new issue