[Profling] reading kibana yml values (#159015)

kibana.yml config definition example
```yaml
xpack.profiling.collector.host: 'foo'
xpack.profiling.collector.secret_token: 'bar'
xpack.profiling.collector.tls_enabled: true
xpack.profiling.collector.tls_supported_protocols: ['baz', 'qux']
xpack.profiling.collector.tls_certificate_path: 'path'
xpack.profiling.collector.tls_key_path: 'key'

xpack.profiling.symbolizer.host: 'foo'
xpack.profiling.symbolizer.secret_token: 'bar'
xpack.profiling.symbolizer.tls_enabled: true
xpack.profiling.symbolizer.tls_supported_protocols: ['baz', 'qux']
xpack.profiling.symbolizer.tls_certificate_path: 'path'
xpack.profiling.symbolizer.tls_key_path: 'key'
```

This PR reads the config defined in the kibana.yml (example above) and
generates the vars object that will be used to create both `profiling
collector` and `profiling symbolizer`.

It also generates the `secret_token` which is a 16-long string
[a-zA-Z0-9].
This commit is contained in:
Cauê Marcondes 2023-06-07 08:32:14 +01:00 committed by GitHub
parent 9f5ecaa913
commit b8b4f75145
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 0 deletions

View file

@ -9,8 +9,19 @@ import { schema, TypeOf } from '@kbn/config-schema';
import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
import { ProfilingPlugin } from './plugin';
const packageInputSchema = schema.object({
host: schema.maybe(schema.string()),
secret_token: schema.maybe(schema.string()),
tls_enabled: schema.maybe(schema.boolean()),
tls_supported_protocols: schema.maybe(schema.arrayOf(schema.string())),
tls_certificate_path: schema.maybe(schema.string()),
tls_key_path: schema.maybe(schema.string()),
});
const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
symbolizer: schema.maybe(packageInputSchema),
collector: schema.maybe(packageInputSchema),
elasticsearch: schema.maybe(
schema.object({
hosts: schema.string(),
@ -21,6 +32,7 @@ const configSchema = schema.object({
});
export type ProfilingConfig = TypeOf<typeof configSchema>;
export type PackageInputType = TypeOf<typeof packageInputSchema>;
// plugin config
export const config: PluginConfigDescriptor<ProfilingConfig> = {

View file

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PackageInputType } from '../..';
import { getVarsFor } from './fleet_policies';
const secretTokenRegex = /^[a-zA-Z0-9]+$/;
describe('getVarsFor', () => {
it('returns secret_token when package input is not provided', () => {
const config: PackageInputType = {};
const { secret_token: secretToken, ...result } = getVarsFor({
config,
includeSecretToken: true,
});
expect(secretToken?.type).toBe('text');
expect(secretToken?.value).toBeDefined();
expect(secretToken?.value).toBeDefined();
expect(secretTokenRegex.test(secretToken?.value)).toBeTruthy();
expect(result).toEqual({});
});
it('returns the vars object for the keys defined plus secret token', () => {
const config: PackageInputType = {
host: 'example.com',
tls_enabled: true,
tls_supported_protocols: ['foo', 'bar'],
tls_certificate_path: '123',
tls_key_path: '456',
};
const { secret_token: secretToken, ...result } = getVarsFor({
config,
includeSecretToken: true,
});
expect(secretToken?.type).toBe('text');
expect(secretToken?.value.length).toBe(16);
expect(secretTokenRegex.test(secretToken?.value)).toBeTruthy();
expect(result).toEqual({
host: { type: 'text', value: 'example.com' },
tls_enabled: { type: 'bool', value: true },
tls_supported_protocols: { type: 'text', value: ['foo', 'bar'] },
tls_certificate_path: { type: 'text', value: '123' },
tls_key_path: { type: 'text', value: '456' },
});
});
it('discards secret_token defined and generate a new one', () => {
const config: PackageInputType = {
host: 'example.com',
tls_enabled: true,
tls_supported_protocols: ['foo', 'bar'],
tls_certificate_path: '123',
tls_key_path: '456',
secret_token: 'bar!',
};
const { secret_token: secretToken, ...result } = getVarsFor({
config,
includeSecretToken: true,
});
expect(secretToken?.type).toBe('text');
expect(secretToken?.value).not.toBe('bar!');
expect(secretToken?.value.length).toBe(16);
expect(secretTokenRegex.test(secretToken?.value)).toBeTruthy();
expect(result).toEqual({
host: { type: 'text', value: 'example.com' },
tls_enabled: { type: 'bool', value: true },
tls_supported_protocols: { type: 'text', value: ['foo', 'bar'] },
tls_certificate_path: { type: 'text', value: '123' },
tls_key_path: { type: 'text', value: '456' },
});
});
it('returns vars without secret_token', () => {
const config: PackageInputType = {
host: 'example.com',
tls_enabled: true,
tls_supported_protocols: ['foo', 'bar'],
tls_certificate_path: '123',
tls_key_path: '456',
};
const { secret_token: secretToken, ...result } = getVarsFor({
config,
includeSecretToken: false,
});
expect(secretToken).toBeUndefined();
expect(result).toEqual({
host: { type: 'text', value: 'example.com' },
tls_enabled: { type: 'bool', value: true },
tls_supported_protocols: { type: 'text', value: ['foo', 'bar'] },
tls_certificate_path: { type: 'text', value: '123' },
tls_key_path: { type: 'text', value: '456' },
});
});
});

View file

@ -11,6 +11,7 @@ import { fetchFindLatestPackageOrThrow } from '@kbn/fleet-plugin/server/services
import { getApmPolicy } from './get_apm_policy';
import { ProfilingSetupOptions } from './types';
import { PartialSetupState } from '../../../common/setup';
import { PackageInputType } from '../..';
async function createIngestAPIKey(esClient: ElasticsearchClient) {
const apiKeyResponse = await esClient.security.createApiKey({
@ -125,10 +126,47 @@ export async function validateCollectorPackagePolicy({
};
}
export function generateSecretToken() {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 16; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}
return result;
}
export function getVarsFor({
config,
includeSecretToken,
}: {
config: PackageInputType;
includeSecretToken: boolean;
}) {
const configKeys = Object.keys(config) as Array<keyof PackageInputType>;
if (includeSecretToken) {
configKeys.push('secret_token');
}
return configKeys.reduce<
Partial<Record<keyof PackageInputType, { type: 'text' | 'bool'; value: any }>>
>((acc, currKey) => {
const value = currKey === 'secret_token' ? generateSecretToken() : config[currKey];
const type = typeof value === 'boolean' ? 'bool' : 'text';
return {
...acc,
[currKey]: { type, value },
};
}, {});
}
export async function createCollectorPackagePolicy({
client,
soClient,
packagePolicyClient,
config,
}: ProfilingSetupOptions) {
const packageName = 'profiler_collector';
const { version } = await fetchFindLatestPackageOrThrow(packageName, { prerelease: true });
@ -148,6 +186,9 @@ export async function createCollectorPackagePolicy({
enabled: true,
streams: [],
type: 'pf-elastic-collector',
vars: config?.collector
? getVarsFor({ config: config.collector, includeSecretToken: true })
: {},
},
],
};
@ -175,6 +216,7 @@ export async function createSymbolizerPackagePolicy({
client,
soClient,
packagePolicyClient,
config,
}: ProfilingSetupOptions) {
const packageName = 'profiler_symbolizer';
const { version } = await fetchFindLatestPackageOrThrow(packageName, { prerelease: true });
@ -194,6 +236,10 @@ export async function createSymbolizerPackagePolicy({
enabled: true,
streams: [],
type: 'pf-elastic-symbolizer',
// doesnt have secret token
vars: config?.symbolizer
? getVarsFor({ config: config.symbolizer, includeSecretToken: false })
: {},
},
],
};

View file

@ -8,6 +8,7 @@
import { SavedObjectsClientContract } from '@kbn/core/server';
import { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import { Logger } from '@kbn/logging';
import { ProfilingConfig } from '../..';
import { ProfilingESClient } from '../../utils/create_profiling_es_client';
export interface ProfilingSetupOptions {
@ -17,4 +18,5 @@ export interface ProfilingSetupOptions {
logger: Logger;
spaceId: string;
isCloudEnabled: boolean;
config: ProfilingConfig;
}

View file

@ -57,6 +57,7 @@ export class ProfilingPlugin
dependencies: {
start: depsStart,
setup: deps,
config,
},
services: {
createProfilingEsClient: ({

View file

@ -8,6 +8,7 @@
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import type { KibanaRequest } from '@kbn/core-http-server';
import type { IRouter, Logger } from '@kbn/core/server';
import { ProfilingConfig } from '..';
import {
ProfilingPluginSetupDeps,
ProfilingPluginStartDeps,
@ -31,6 +32,7 @@ export interface RouteRegisterParameters {
dependencies: {
start: ProfilingPluginStartDeps;
setup: ProfilingPluginSetupDeps;
config: ProfilingConfig;
};
services: {
createProfilingEsClient: (params: {

View file

@ -63,6 +63,7 @@ export function registerSetupRoute({
soClient: core.savedObjects.client,
spaceId: dependencies.setup.spaces.spacesService.getSpaceId(request),
isCloudEnabled: dependencies.setup.cloud.isCloudEnabled,
config: dependencies.config,
};
logger.info('Checking if Elasticsearch and Fleet are setup for Universal Profiling');
@ -127,6 +128,7 @@ export function registerSetupRoute({
soClient: core.savedObjects.client,
spaceId: dependencies.setup.spaces.spacesService.getSpaceId(request),
isCloudEnabled: dependencies.setup.cloud.isCloudEnabled,
config: dependencies.config,
};
logger.info('Setting up Elasticsearch and Fleet for Universal Profiling');