Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Monitoring] Ensure we use existing Elasticsearch config #68389

Merged
merged 11 commits into from
Jun 15, 2020
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';
chrisronline marked this conversation as resolved.
Show resolved Hide resolved
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 })])),
igoristic marked this conversation as resolved.
Show resolved Hide resolved
});

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_system" 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