Skip to content

Commit

Permalink
[Monitoring] Ensure we use existing Elasticsearch config (elastic#68389)
Browse files Browse the repository at this point in the history
* Ensure we use existing Elasticsearch config

* Use separate type for this to ensure custom properties work

* PR suggestions

* PR feedback

* PR feedback

* Fix type issues

* PR feedback
  • Loading branch information
chrisronline committed Jun 15, 2020
1 parent 9601b3e commit 28c0b2b
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 185 deletions.
164 changes: 164 additions & 0 deletions x-pack/plugins/monitoring/server/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createConfig, configSchema } from './config';
jest.mock('fs', () => ({
...jest.requireActual('fs'),
readFileSync: jest.fn().mockImplementation((path: string) => `contents-of-${path}`),
}));

describe('config schema', () => {
it('generates proper defaults', () => {
expect(configSchema.validate({})).toMatchInlineSnapshot(`
Object {
"agent": Object {
"interval": "10s",
},
"cluster_alerts": Object {
"email_notifications": Object {
"email_address": "",
"enabled": true,
},
"enabled": true,
},
"elasticsearch": Object {
"apiVersion": "master",
"customHeaders": Object {},
"healthCheck": Object {
"delay": "PT2.5S",
},
"ignoreVersionMismatch": false,
"logFetchCount": 10,
"logQueries": false,
"pingTimeout": "PT30S",
"preserveHost": true,
"requestHeadersWhitelist": Array [
"authorization",
],
"requestTimeout": "PT30S",
"shardTimeout": "PT30S",
"sniffInterval": false,
"sniffOnConnectionFault": false,
"sniffOnStart": false,
"ssl": Object {
"alwaysPresentCertificate": false,
"keystore": Object {},
"truststore": Object {},
"verificationMode": "full",
},
"startupTimeout": "PT5S",
},
"enabled": true,
"kibana": Object {
"collection": Object {
"enabled": true,
"interval": 10000,
},
},
"licensing": Object {
"api_polling_frequency": "PT30S",
},
"tests": Object {
"cloud_detector": Object {
"enabled": true,
},
},
"ui": Object {
"ccs": Object {
"enabled": true,
},
"container": Object {
"elasticsearch": Object {
"enabled": false,
},
"logstash": Object {
"enabled": false,
},
},
"elasticsearch": Object {
"apiVersion": "master",
"customHeaders": Object {},
"healthCheck": Object {
"delay": "PT2.5S",
},
"ignoreVersionMismatch": false,
"logFetchCount": 10,
"logQueries": false,
"pingTimeout": "PT30S",
"preserveHost": true,
"requestHeadersWhitelist": Array [
"authorization",
],
"requestTimeout": "PT30S",
"shardTimeout": "PT30S",
"sniffInterval": false,
"sniffOnConnectionFault": false,
"sniffOnStart": false,
"ssl": Object {
"alwaysPresentCertificate": false,
"keystore": Object {},
"truststore": Object {},
"verificationMode": "full",
},
"startupTimeout": "PT5S",
},
"enabled": true,
"logs": Object {
"index": "filebeat-*",
},
"max_bucket_size": 10000,
"min_interval_seconds": 10,
"show_license_expiration": true,
},
}
`);
});
});

describe('createConfig()', () => {
it('should wrap in Elasticsearch config', async () => {
const config = createConfig(
configSchema.validate({
elasticsearch: {
hosts: 'http://localhost:9200',
},
ui: {
elasticsearch: {
hosts: 'http://localhost:9200',
},
},
})
);
expect(config.elasticsearch.hosts).toEqual(['http://localhost:9200']);
expect(config.ui.elasticsearch.hosts).toEqual(['http://localhost:9200']);
});

it('should attempt to read PEM files', async () => {
const ssl = {
certificate: 'packages/kbn-dev-utils/certs/elasticsearch.crt',
key: 'packages/kbn-dev-utils/certs/elasticsearch.key',
certificateAuthorities: 'packages/kbn-dev-utils/certs/ca.crt',
};
const config = createConfig(
configSchema.validate({
elasticsearch: {
ssl,
},
ui: {
elasticsearch: {
ssl,
},
},
})
);
const expected = expect.objectContaining({
certificate: 'contents-of-packages/kbn-dev-utils/certs/elasticsearch.crt',
key: 'contents-of-packages/kbn-dev-utils/certs/elasticsearch.key',
certificateAuthorities: ['contents-of-packages/kbn-dev-utils/certs/ca.crt'],
});
expect(config.elasticsearch.ssl).toEqual(expected);
expect(config.ui.elasticsearch.ssl).toEqual(expected);
});
});
203 changes: 34 additions & 169 deletions x-pack/plugins/monitoring/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
config as ElasticsearchBaseConfig,
ElasticsearchConfig,
} from '../../../../src/core/server/';

const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
const DEFAULT_API_VERSION = 'master';

const elasticsearchConfigSchema = ElasticsearchBaseConfig.elasticsearch.schema;
type ElasticsearchConfigType = TypeOf<typeof elasticsearchConfigSchema>;

export const monitoringElasticsearchConfigSchema = elasticsearchConfigSchema.extends({
logFetchCount: schema.number({ defaultValue: 10 }),
hosts: schema.maybe(schema.oneOf([hostURISchema, schema.arrayOf(hostURISchema, { minSize: 1 })])),
});

export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
elasticsearch: schema.object({
logFetchCount: schema.number({ defaultValue: 10 }),
sniffOnStart: schema.boolean({ defaultValue: false }),
sniffInterval: schema.oneOf([schema.duration(), schema.literal(false)], {
defaultValue: false,
}),
sniffOnConnectionFault: schema.boolean({ defaultValue: false }),
hosts: schema.maybe(
schema.oneOf([hostURISchema, schema.arrayOf(hostURISchema, { minSize: 1 })])
),
preserveHost: schema.boolean({ defaultValue: true }),
username: schema.maybe(
schema.conditional(
schema.contextRef('dist'),
false,
schema.string({
validate: () => {},
}),
schema.string()
)
),
password: schema.maybe(schema.string()),
requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], {
defaultValue: ['authorization'],
}),
customHeaders: schema.recordOf(schema.string(), schema.string(), { defaultValue: {} }),
shardTimeout: schema.duration({ defaultValue: '30s' }),
requestTimeout: schema.duration({ defaultValue: '30s' }),
pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }),
startupTimeout: schema.duration({ defaultValue: '5s' }),
logQueries: schema.boolean({ defaultValue: false }),
ssl: schema.object(
{
verificationMode: schema.oneOf(
[schema.literal('none'), schema.literal('certificate'), schema.literal('full')],
{ defaultValue: 'full' }
),
certificateAuthorities: schema.maybe(
schema.oneOf([schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })])
),
certificate: schema.maybe(schema.string()),
key: schema.maybe(schema.string()),
keyPassphrase: schema.maybe(schema.string()),
keystore: schema.object({
path: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
}),
truststore: schema.object({
path: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
}),
alwaysPresentCertificate: schema.boolean({ defaultValue: false }),
},
{
validate: (rawConfig) => {
if (rawConfig.key && rawConfig.keystore.path) {
return 'cannot use [key] when [keystore.path] is specified';
}
if (rawConfig.certificate && rawConfig.keystore.path) {
return 'cannot use [certificate] when [keystore.path] is specified';
}
},
}
),
apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }),
healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }),
ignoreVersionMismatch: schema.conditional(
schema.contextRef('dev'),
false,
schema.boolean({
validate: (rawValue) => {
if (rawValue === true) {
return '"ignoreVersionMismatch" can only be set to true in development mode';
}
},
defaultValue: false,
}),
schema.boolean({ defaultValue: false })
),
}),
elasticsearch: monitoringElasticsearchConfigSchema,
ui: schema.object({
enabled: schema.boolean({ defaultValue: true }),
ccs: schema.object({
Expand All @@ -99,93 +31,7 @@ export const configSchema = schema.object({
index: schema.string({ defaultValue: 'filebeat-*' }),
}),
max_bucket_size: schema.number({ defaultValue: 10000 }),
elasticsearch: schema.object({
logFetchCount: schema.number({ defaultValue: 10 }),
sniffOnStart: schema.boolean({ defaultValue: false }),
sniffInterval: schema.oneOf([schema.duration(), schema.literal(false)], {
defaultValue: false,
}),
sniffOnConnectionFault: schema.boolean({ defaultValue: false }),
hosts: schema.maybe(
schema.oneOf([hostURISchema, schema.arrayOf(hostURISchema, { minSize: 1 })])
),
preserveHost: schema.boolean({ defaultValue: true }),
username: schema.maybe(
schema.conditional(
schema.contextRef('dist'),
false,
schema.string({
validate: (rawConfig) => {
if (rawConfig === 'elastic') {
return (
'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' +
'privilege-related issues. You should use the "kibana" user instead.'
);
}
},
}),
schema.string()
)
),
password: schema.maybe(schema.string()),
requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], {
defaultValue: ['authorization'],
}),
customHeaders: schema.recordOf(schema.string(), schema.string(), { defaultValue: {} }),
shardTimeout: schema.duration({ defaultValue: '30s' }),
requestTimeout: schema.duration({ defaultValue: '30s' }),
pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }),
startupTimeout: schema.duration({ defaultValue: '5s' }),
logQueries: schema.boolean({ defaultValue: false }),
ssl: schema.object(
{
verificationMode: schema.oneOf(
[schema.literal('none'), schema.literal('certificate'), schema.literal('full')],
{ defaultValue: 'full' }
),
certificateAuthorities: schema.maybe(
schema.oneOf([schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })])
),
certificate: schema.maybe(schema.string()),
key: schema.maybe(schema.string()),
keyPassphrase: schema.maybe(schema.string()),
keystore: schema.object({
path: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
}),
truststore: schema.object({
path: schema.maybe(schema.string()),
password: schema.maybe(schema.string()),
}),
alwaysPresentCertificate: schema.boolean({ defaultValue: false }),
},
{
validate: (rawConfig) => {
if (rawConfig.key && rawConfig.keystore.path) {
return 'cannot use [key] when [keystore.path] is specified';
}
if (rawConfig.certificate && rawConfig.keystore.path) {
return 'cannot use [certificate] when [keystore.path] is specified';
}
},
}
),
apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }),
healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }),
ignoreVersionMismatch: schema.conditional(
schema.contextRef('dev'),
false,
schema.boolean({
validate: (rawValue) => {
if (rawValue === true) {
return '"ignoreVersionMismatch" can only be set to true in development mode';
}
},
defaultValue: false,
}),
schema.boolean({ defaultValue: false })
),
}),
elasticsearch: monitoringElasticsearchConfigSchema,
container: schema.object({
elasticsearch: schema.object({
enabled: schema.boolean({ defaultValue: false }),
Expand Down Expand Up @@ -227,4 +73,23 @@ export const configSchema = schema.object({
}),
});

export type MonitoringConfig = TypeOf<typeof configSchema>;
export class MonitoringElasticsearchConfig extends ElasticsearchConfig {
public readonly logFetchCount?: number;

constructor(rawConfig: TypeOf<typeof monitoringElasticsearchConfigSchema>) {
super(rawConfig as ElasticsearchConfigType);
this.logFetchCount = rawConfig.logFetchCount;
}
}

export type MonitoringConfig = ReturnType<typeof createConfig>;
export function createConfig(config: TypeOf<typeof configSchema>) {
return {
...config,
elasticsearch: new ElasticsearchConfig(config.elasticsearch as ElasticsearchConfigType),
ui: {
...config.ui,
elasticsearch: new MonitoringElasticsearchConfig(config.ui.elasticsearch),
},
};
}
Loading

0 comments on commit 28c0b2b

Please sign in to comment.