[Reporting] Switch Serverside Config Wrapper to NP (#62500)

* New config

* fix translations json

* add csv.useByteOrderMarkEncoding to schema

* imports cleanup

* restore "get default chromium sandbox disabled" functionality

* integrate getDefaultChromiumSandboxDisabled

* fix tests

* --wip-- [skip ci]

* add more schema tests

* diff prettiness

* trash legacy files that moved to NP

* create_config tests

* Hoist create_config

* better disableSandbox tests

* fix ts

* fix export

* fix bad code

* make comments better

* fix i18n

* comment

* automatically setting... logs

* replace log_configuration

* fix lint

* This is f2

* improve startup log about sandbox info

* update docs with log reference

* revert log removal

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Tim Sullivan 2020-04-15 14:52:32 -07:00 committed by GitHub
parent 23e3f1aab5
commit f4c81b440d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 709 additions and 842 deletions

View file

@ -11,12 +11,12 @@ sandboxing techniques differ for each operating system.
The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many
distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. {reporting}
will automatically disable the sandbox when it is running on Debian and CentOS as additional steps are required to enable
unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} logs:
`Enabling the Chromium sandbox provides an additional layer of protection`.
unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs:
`Chromium sandbox provides an additional layer of protection, but is not supported for your OS.
Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.`
If your kernel is 3.8 or newer, it's
recommended to enable usernamespaces and set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your
`kibana.yml` to enable the sandbox.
Reporting will automatically enable the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's
recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces.
==== Docker
When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and

View file

@ -1,387 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`config schema with context {"dev":false,"dist":false} produces correct config 1`] = `
Object {
"capture": Object {
"browser": Object {
"autoDownload": true,
"chromium": Object {
"disableSandbox": "<platform dependent>",
"maxScreenshotDimension": 1950,
"proxy": Object {
"enabled": false,
},
},
"type": "chromium",
},
"concurrency": 4,
"loadDelay": 3000,
"maxAttempts": 1,
"networkPolicy": Object {
"enabled": true,
"rules": Array [
Object {
"allow": true,
"protocol": "http:",
},
Object {
"allow": true,
"protocol": "https:",
},
Object {
"allow": true,
"protocol": "ws:",
},
Object {
"allow": true,
"protocol": "wss:",
},
Object {
"allow": true,
"protocol": "data:",
},
Object {
"allow": false,
},
],
},
"settleTime": 1000,
"timeout": 20000,
"timeouts": Object {
"openUrl": 30000,
"renderComplete": 30000,
"waitForElements": 30000,
},
"viewport": Object {
"height": 1200,
"width": 1950,
},
"zoom": 2,
},
"csv": Object {
"checkForFormulas": true,
"enablePanelActionDownload": true,
"maxSizeBytes": 10485760,
"scroll": Object {
"duration": "30s",
"size": 500,
},
"useByteOrderMarkEncoding": false,
},
"enabled": true,
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"index": ".reporting",
"kibanaServer": Object {},
"poll": Object {
"jobCompletionNotifier": Object {
"interval": 10000,
"intervalErrorMultiplier": 5,
},
"jobsRefresh": Object {
"interval": 5000,
"intervalErrorMultiplier": 5,
},
},
"queue": Object {
"indexInterval": "week",
"pollEnabled": true,
"pollInterval": 3000,
"pollIntervalErrorMultiplier": 10,
"timeout": 120000,
},
"roles": Object {
"allow": Array [
"reporting_user",
],
},
}
`;
exports[`config schema with context {"dev":false,"dist":true} produces correct config 1`] = `
Object {
"capture": Object {
"browser": Object {
"autoDownload": false,
"chromium": Object {
"disableSandbox": "<platform dependent>",
"maxScreenshotDimension": 1950,
"proxy": Object {
"enabled": false,
},
},
"type": "chromium",
},
"concurrency": 4,
"loadDelay": 3000,
"maxAttempts": 3,
"networkPolicy": Object {
"enabled": true,
"rules": Array [
Object {
"allow": true,
"protocol": "http:",
},
Object {
"allow": true,
"protocol": "https:",
},
Object {
"allow": true,
"protocol": "ws:",
},
Object {
"allow": true,
"protocol": "wss:",
},
Object {
"allow": true,
"protocol": "data:",
},
Object {
"allow": false,
},
],
},
"settleTime": 1000,
"timeout": 20000,
"timeouts": Object {
"openUrl": 30000,
"renderComplete": 30000,
"waitForElements": 30000,
},
"viewport": Object {
"height": 1200,
"width": 1950,
},
"zoom": 2,
},
"csv": Object {
"checkForFormulas": true,
"enablePanelActionDownload": true,
"maxSizeBytes": 10485760,
"scroll": Object {
"duration": "30s",
"size": 500,
},
"useByteOrderMarkEncoding": false,
},
"enabled": true,
"index": ".reporting",
"kibanaServer": Object {},
"poll": Object {
"jobCompletionNotifier": Object {
"interval": 10000,
"intervalErrorMultiplier": 5,
},
"jobsRefresh": Object {
"interval": 5000,
"intervalErrorMultiplier": 5,
},
},
"queue": Object {
"indexInterval": "week",
"pollEnabled": true,
"pollInterval": 3000,
"pollIntervalErrorMultiplier": 10,
"timeout": 120000,
},
"roles": Object {
"allow": Array [
"reporting_user",
],
},
}
`;
exports[`config schema with context {"dev":true,"dist":false} produces correct config 1`] = `
Object {
"capture": Object {
"browser": Object {
"autoDownload": true,
"chromium": Object {
"disableSandbox": "<platform dependent>",
"maxScreenshotDimension": 1950,
"proxy": Object {
"enabled": false,
},
},
"type": "chromium",
},
"concurrency": 4,
"loadDelay": 3000,
"maxAttempts": 1,
"networkPolicy": Object {
"enabled": true,
"rules": Array [
Object {
"allow": true,
"protocol": "http:",
},
Object {
"allow": true,
"protocol": "https:",
},
Object {
"allow": true,
"protocol": "ws:",
},
Object {
"allow": true,
"protocol": "wss:",
},
Object {
"allow": true,
"protocol": "data:",
},
Object {
"allow": false,
},
],
},
"settleTime": 1000,
"timeout": 20000,
"timeouts": Object {
"openUrl": 30000,
"renderComplete": 30000,
"waitForElements": 30000,
},
"viewport": Object {
"height": 1200,
"width": 1950,
},
"zoom": 2,
},
"csv": Object {
"checkForFormulas": true,
"enablePanelActionDownload": true,
"maxSizeBytes": 10485760,
"scroll": Object {
"duration": "30s",
"size": 500,
},
"useByteOrderMarkEncoding": false,
},
"enabled": true,
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"index": ".reporting",
"kibanaServer": Object {},
"poll": Object {
"jobCompletionNotifier": Object {
"interval": 10000,
"intervalErrorMultiplier": 5,
},
"jobsRefresh": Object {
"interval": 5000,
"intervalErrorMultiplier": 5,
},
},
"queue": Object {
"indexInterval": "week",
"pollEnabled": true,
"pollInterval": 3000,
"pollIntervalErrorMultiplier": 10,
"timeout": 120000,
},
"roles": Object {
"allow": Array [
"reporting_user",
],
},
}
`;
exports[`config schema with context {"dev":true,"dist":true} produces correct config 1`] = `
Object {
"capture": Object {
"browser": Object {
"autoDownload": false,
"chromium": Object {
"disableSandbox": "<platform dependent>",
"maxScreenshotDimension": 1950,
"proxy": Object {
"enabled": false,
},
},
"type": "chromium",
},
"concurrency": 4,
"loadDelay": 3000,
"maxAttempts": 3,
"networkPolicy": Object {
"enabled": true,
"rules": Array [
Object {
"allow": true,
"protocol": "http:",
},
Object {
"allow": true,
"protocol": "https:",
},
Object {
"allow": true,
"protocol": "ws:",
},
Object {
"allow": true,
"protocol": "wss:",
},
Object {
"allow": true,
"protocol": "data:",
},
Object {
"allow": false,
},
],
},
"settleTime": 1000,
"timeout": 20000,
"timeouts": Object {
"openUrl": 30000,
"renderComplete": 30000,
"waitForElements": 30000,
},
"viewport": Object {
"height": 1200,
"width": 1950,
},
"zoom": 2,
},
"csv": Object {
"checkForFormulas": true,
"enablePanelActionDownload": true,
"maxSizeBytes": 10485760,
"scroll": Object {
"duration": "30s",
"size": 500,
},
"useByteOrderMarkEncoding": false,
},
"enabled": true,
"index": ".reporting",
"kibanaServer": Object {},
"poll": Object {
"jobCompletionNotifier": Object {
"interval": 10000,
"intervalErrorMultiplier": 5,
},
"jobsRefresh": Object {
"interval": 5000,
"intervalErrorMultiplier": 5,
},
},
"queue": Object {
"indexInterval": "week",
"pollEnabled": true,
"pollInterval": 3000,
"pollIntervalErrorMultiplier": 10,
"timeout": 120000,
},
"roles": Object {
"allow": Array [
"reporting_user",
],
},
}
`;

View file

@ -1,183 +0,0 @@
/*
* 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 { BROWSER_TYPE } from './common/constants';
// @ts-ignore untyped module
import { config as appConfig } from './server/config/config';
import { getDefaultChromiumSandboxDisabled } from './server/browsers';
export async function config(Joi: any) {
return Joi.object({
enabled: Joi.boolean().default(true),
kibanaServer: Joi.object({
protocol: Joi.string().valid(['http', 'https']),
hostname: Joi.string().invalid('0'),
port: Joi.number().integer(),
}).default(),
queue: Joi.object({
indexInterval: Joi.string().default('week'),
pollEnabled: Joi.boolean().default(true),
pollInterval: Joi.number()
.integer()
.default(3000),
pollIntervalErrorMultiplier: Joi.number()
.integer()
.default(10),
timeout: Joi.number()
.integer()
.default(120000),
}).default(),
capture: Joi.object({
timeouts: Joi.object({
openUrl: Joi.number()
.integer()
.default(30000),
waitForElements: Joi.number()
.integer()
.default(30000),
renderComplete: Joi.number()
.integer()
.default(30000),
}).default(),
networkPolicy: Joi.object({
enabled: Joi.boolean().default(true),
rules: Joi.array()
.items(
Joi.object({
allow: Joi.boolean().required(),
protocol: Joi.string(),
host: Joi.string(),
})
)
.default([
{ allow: true, protocol: 'http:' },
{ allow: true, protocol: 'https:' },
{ allow: true, protocol: 'ws:' },
{ allow: true, protocol: 'wss:' },
{ allow: true, protocol: 'data:' },
{ allow: false }, // Default action is to deny!
]),
}).default(),
zoom: Joi.number()
.integer()
.default(2),
viewport: Joi.object({
width: Joi.number()
.integer()
.default(1950),
height: Joi.number()
.integer()
.default(1200),
}).default(),
timeout: Joi.number()
.integer()
.default(20000), // deprecated
loadDelay: Joi.number()
.integer()
.default(3000),
settleTime: Joi.number()
.integer()
.default(1000), // deprecated
concurrency: Joi.number()
.integer()
.default(appConfig.concurrency), // deprecated
browser: Joi.object({
type: Joi.any()
.valid(BROWSER_TYPE)
.default(BROWSER_TYPE),
autoDownload: Joi.boolean().when('$dist', {
is: true,
then: Joi.default(false),
otherwise: Joi.default(true),
}),
chromium: Joi.object({
inspect: Joi.boolean()
.when('$dev', {
is: false,
then: Joi.valid(false),
else: Joi.default(false),
})
.default(),
disableSandbox: Joi.boolean().default(await getDefaultChromiumSandboxDisabled()),
proxy: Joi.object({
enabled: Joi.boolean().default(false),
server: Joi.string()
.uri({ scheme: ['http', 'https'] })
.when('enabled', {
is: Joi.valid(false),
then: Joi.valid(null),
else: Joi.required(),
}),
bypass: Joi.array()
.items(Joi.string().regex(/^[^\s]+$/))
.when('enabled', {
is: Joi.valid(false),
then: Joi.valid(null),
else: Joi.default([]),
}),
}).default(),
maxScreenshotDimension: Joi.number()
.integer()
.default(1950),
}).default(),
}).default(),
maxAttempts: Joi.number()
.integer()
.greater(0)
.when('$dist', {
is: true,
then: Joi.default(3),
otherwise: Joi.default(1),
})
.default(),
}).default(),
csv: Joi.object({
useByteOrderMarkEncoding: Joi.boolean().default(false),
checkForFormulas: Joi.boolean().default(true),
enablePanelActionDownload: Joi.boolean().default(true),
maxSizeBytes: Joi.number()
.integer()
.default(1024 * 1024 * 10), // bytes in a kB * kB in a mB * 10
scroll: Joi.object({
duration: Joi.string()
.regex(/^[0-9]+(d|h|m|s|ms|micros|nanos)$/, { name: 'DurationString' })
.default('30s'),
size: Joi.number()
.integer()
.default(500),
}).default(),
}).default(),
encryptionKey: Joi.when(Joi.ref('$dist'), {
is: true,
then: Joi.string(),
otherwise: Joi.string().default('a'.repeat(32)),
}),
roles: Joi.object({
allow: Joi.array()
.items(Joi.string())
.default(['reporting_user']),
}).default(),
index: Joi.string().default('.reporting'),
poll: Joi.object({
jobCompletionNotifier: Joi.object({
interval: Joi.number()
.integer()
.default(10000),
intervalErrorMultiplier: Joi.number()
.integer()
.default(5),
}).default(),
jobsRefresh: Joi.object({
interval: Joi.number()
.integer()
.default(5000),
intervalErrorMultiplier: Joi.number()
.integer()
.default(5),
}).default(),
}).default(),
}).default();
}

View file

@ -1,34 +0,0 @@
/*
* 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 { reporting } from './index';
import { getConfigSchema } from '../../../test_utils';
// The snapshot records the number of cpus available
// to make the snapshot deterministic `os.cpus` needs to be mocked
// but the other members on `os` must remain untouched
jest.mock('os', () => {
const os = jest.requireActual('os');
os.cpus = () => [{}, {}, {}, {}];
return os;
});
// eslint-disable-next-line jest/valid-describe
const describeWithContext = describe.each([
[{ dev: false, dist: false }],
[{ dev: true, dist: false }],
[{ dev: false, dist: true }],
[{ dev: true, dist: true }],
]);
describeWithContext('config schema with context %j', context => {
it('produces correct config', async () => {
const schema = await getConfigSchema(reporting);
const value: any = await schema.validate({}, { context });
value.capture.browser.chromium.disableSandbox = '<platform dependent>';
await expect(value).toMatchSnapshot();
});
});

View file

@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n';
import { Legacy } from 'kibana';
import { resolve } from 'path';
import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants';
import { config as reportingConfig } from './config';
import { legacyInit } from './server/legacy';
import { ReportingPluginSpecOptions } from './types';
@ -17,10 +16,8 @@ const kbToBase64Length = (kb: number) => Math.floor((kb * 1024 * 8) / 6);
export const reporting = (kibana: any) => {
return new kibana.Plugin({
id: PLUGIN_ID,
configPrefix: 'xpack.reporting',
publicDir: resolve(__dirname, 'public'),
require: ['kibana', 'elasticsearch', 'xpack_main'],
config: reportingConfig,
uiExports: {
uiSettingDefaults: {
@ -47,14 +44,5 @@ export const reporting = (kibana: any) => {
async init(server: Legacy.Server) {
return legacyInit(server, this);
},
deprecations({ unused }: any) {
return [
unused('capture.concurrency'),
unused('capture.timeout'),
unused('capture.settleTime'),
unused('kibanaApp'),
];
},
} as ReportingPluginSpecOptions);
};

View file

@ -1,35 +0,0 @@
/*
* 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 getosSync, { LinuxOs } from 'getos';
import { promisify } from 'util';
import { BROWSER_TYPE } from './common/constants';
import { CaptureConfig } from './server/types';
import { Logger } from './types';
const getos = promisify(getosSync);
export async function logConfiguration(captureConfig: CaptureConfig, logger: Logger) {
const {
browser: {
type: browserType,
chromium: { disableSandbox },
},
} = captureConfig;
logger.debug(`Browser type: ${browserType}`);
if (browserType === BROWSER_TYPE) {
logger.debug(`Chromium sandbox disabled: ${disableSandbox}`);
}
const os = await getos();
const { os: osName, dist, release } = os as LinuxOs;
if (dist) {
logger.debug(`Running on os "${osName}", distribution "${dist}", release "${release}"`);
} else {
logger.debug(`Running on os "${osName}"`);
}
}

View file

@ -10,7 +10,7 @@ type ViewportConfig = CaptureConfig['viewport'];
type BrowserConfig = CaptureConfig['browser']['chromium'];
interface LaunchArgs {
userDataDir: BrowserConfig['userDataDir'];
userDataDir: string;
viewport: ViewportConfig;
disableSandbox: BrowserConfig['disableSandbox'];
proxy: BrowserConfig['proxy'];

View file

@ -8,7 +8,6 @@ import * as chromiumDefinition from './chromium';
export { ensureAllBrowsersDownloaded } from './download';
export { createBrowserDriverFactory } from './create_browser_driver_factory';
export { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
export { HeadlessChromiumDriver } from './chromium/driver';
export { HeadlessChromiumDriverFactory } from './chromium/driver_factory';

View file

@ -1,21 +0,0 @@
/*
* 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 { cpus } from 'os';
const defaultCPUCount = 2;
function cpuCount() {
try {
return cpus().length;
} catch (e) {
return defaultCPUCount;
}
}
export const config = {
concurrency: cpuCount(),
};

View file

@ -6,10 +6,9 @@
import { Legacy } from 'kibana';
import { CoreSetup } from 'src/core/server';
import { i18n } from '@kbn/i18n';
import crypto from 'crypto';
import { get } from 'lodash';
import { NetworkPolicy } from '../../types';
import { ConfigType as ReportingConfigType } from '../../../../../plugins/reporting/server';
export { ReportingConfigType };
// make config.get() aware of the value type it returns
interface Config<BaseType> {
@ -56,131 +55,6 @@ export interface ReportingConfig extends Config<ReportingConfigType> {
kbnConfig: Config<KbnServerConfigType>;
}
type BrowserType = 'chromium';
interface BrowserConfig {
inspect: boolean;
userDataDir: string;
viewport: { width: number; height: number };
disableSandbox: boolean;
proxy: {
enabled: boolean;
server?: string;
bypass?: string[];
};
}
interface CaptureConfig {
browser: {
type: BrowserType;
autoDownload: boolean;
chromium: BrowserConfig;
};
maxAttempts: number;
networkPolicy: NetworkPolicy;
loadDelay: number;
timeouts: {
openUrl: number;
waitForElements: number;
renderComplete: number;
};
viewport: any;
zoom: any;
}
interface QueueConfig {
indexInterval: string;
pollEnabled: boolean;
pollInterval: number;
pollIntervalErrorMultiplier: number;
timeout: number;
}
interface ScrollConfig {
duration: string;
size: number;
}
export interface ReportingConfigType {
capture: CaptureConfig;
csv: {
scroll: ScrollConfig;
enablePanelActionDownload: boolean;
checkForFormulas: boolean;
maxSizeBytes: number;
useByteOrderMarkEncoding: boolean;
};
encryptionKey: string;
kibanaServer: any;
index: string;
queue: QueueConfig;
roles: any;
}
const addConfigDefaults = (
server: Legacy.Server,
core: CoreSetup,
baseConfig: ReportingConfigType
) => {
// encryption key
let encryptionKey = baseConfig.encryptionKey;
if (encryptionKey === undefined) {
server.log(
['reporting', 'config', 'warning'],
i18n.translate('xpack.reporting.selfCheckEncryptionKey.warning', {
defaultMessage:
`Generating a random key for {setting}. To prevent pending reports ` +
`from failing on restart, please set {setting} in kibana.yml`,
values: {
setting: 'xpack.reporting.encryptionKey',
},
})
);
encryptionKey = crypto.randomBytes(16).toString('hex');
}
const { kibanaServer: reportingServer } = baseConfig;
const serverInfo = core.http.getServerInfo();
// kibanaServer.hostname, default to server.host, don't allow "0"
let kibanaServerHostname = reportingServer.hostname ? reportingServer.hostname : serverInfo.host;
if (kibanaServerHostname === '0') {
server.log(
['reporting', 'config', 'warning'],
i18n.translate('xpack.reporting.selfCheckHostname.warning', {
defaultMessage:
`Found 'server.host: "0"' in settings. This is incompatible with Reporting. ` +
`To enable Reporting to work, '{setting}: 0.0.0.0' is being automatically to the configuration. ` +
`You can change to 'server.host: 0.0.0.0' or add '{setting}: 0.0.0.0' in kibana.yml to prevent this message.`,
values: {
setting: 'xpack.reporting.kibanaServer.hostname',
},
})
);
kibanaServerHostname = '0.0.0.0';
}
// kibanaServer.port, default to server.port
const kibanaServerPort = reportingServer.port
? reportingServer.port
: serverInfo.port; // prettier-ignore
// kibanaServer.protocol, default to server.protocol
const kibanaServerProtocol = reportingServer.protocol
? reportingServer.protocol
: serverInfo.protocol;
return {
...baseConfig,
encryptionKey,
kibanaServer: {
hostname: kibanaServerHostname,
port: kibanaServerPort,
protocol: kibanaServerProtocol,
},
};
};
export const buildConfig = (
core: CoreSetup,
server: Legacy.Server,
@ -204,10 +78,8 @@ export const buildConfig = (
},
};
// spreading arguments as an array allows the return type to be known by the compiler
reportingConfig = addConfigDefaults(server, core, reportingConfig);
return {
get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null),
get: (...keys: string[]) => get(reportingConfig, keys.join('.'), null), // spreading arguments as an array allows the return type to be known by the compiler
kbnConfig: {
get: (...keys: string[]) => get(kbnConfig, keys.join('.'), null),
},

View file

@ -5,7 +5,9 @@
*/
import { Legacy } from 'kibana';
import { take } from 'rxjs/operators';
import { PluginInitializerContext } from 'src/core/server';
import { PluginsSetup } from '../../../../plugins/reporting/server';
import { SecurityPluginSetup } from '../../../../plugins/security/server';
import { ReportingPluginSpecOptions } from '../types';
import { buildConfig } from './config';
@ -17,7 +19,6 @@ const buildLegacyDependencies = (
reportingPlugin: ReportingPluginSpecOptions
): LegacySetup => ({
route: server.route.bind(server),
config: server.config,
plugins: {
xpack_main: server.plugins.xpack_main,
reporting: reportingPlugin,
@ -32,14 +33,13 @@ export const legacyInit = async (
reportingLegacyPlugin: ReportingPluginSpecOptions
) => {
const { core: coreSetup } = server.newPlatform.setup;
const legacyConfig = server.config();
const reportingConfig = buildConfig(coreSetup, server, legacyConfig.get('xpack.reporting'));
const { config$ } = (server.newPlatform.setup.plugins.reporting as PluginsSetup).__legacy;
const reportingConfig = await config$.pipe(take(1)).toPromise();
const __LEGACY = buildLegacyDependencies(server, reportingLegacyPlugin);
const pluginInstance = plugin(
server.newPlatform.coreContext as PluginInitializerContext,
reportingConfig
buildConfig(coreSetup, server, reportingConfig)
);
await pluginInstance.setup(coreSetup, {
elasticsearch: coreSetup.elasticsearch,

View file

@ -8,6 +8,7 @@ import moment from 'moment';
export const intervals = ['year', 'month', 'week', 'day', 'hour', 'minute'];
// TODO: This helper function can be removed by using `schema.duration` objects in the reporting config schema
export function indexTimestamp(intervalStr, separator = '-') {
if (separator.match(/[a-z]/i)) throw new Error('Interval separator can not be a letter');

View file

@ -5,15 +5,12 @@
*/
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server';
import { logConfiguration } from '../log_configuration';
import { createBrowserDriverFactory } from './browsers';
import { ReportingCore, ReportingConfig } from './core';
import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib';
import { setFieldFormats } from './services';
import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types';
import { registerReportingUsageCollector } from './usage';
// @ts-ignore no module definition
import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status';
export class ReportingPlugin
implements Plugin<ReportingSetup, ReportingStart, ReportingSetupDeps, ReportingStartDeps> {
@ -61,8 +58,6 @@ export class ReportingPlugin
setFieldFormats(plugins.data.fieldFormats);
logConfiguration(this.config.get('capture'), this.logger);
return {};
}

View file

@ -11,7 +11,7 @@ import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/
import { SecurityPluginSetup } from '../../../../plugins/security/server';
import { XPackMainPlugin } from '../../xpack_main/server/xpack_main';
import { ReportingPluginSpecOptions } from '../types';
import { ReportingConfig, ReportingConfigType } from './core';
import { ReportingConfigType } from './core';
export interface ReportingSetupDeps {
elasticsearch: ElasticsearchServiceSetup;
@ -30,7 +30,6 @@ export type ReportingSetup = object;
export type ReportingStart = object;
export interface LegacySetup {
config: Legacy.Server['config'];
plugins: {
xpack_main: XPackMainPlugin & {
status?: any;

View file

@ -11,7 +11,6 @@ jest.mock('../server/browsers');
jest.mock('../server/lib/create_queue');
jest.mock('../server/lib/enqueue_job');
jest.mock('../server/lib/validate');
jest.mock('../log_configuration');
import { EventEmitter } from 'events';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
export const reportingPollConfig = {
jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 },
jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 },
};

View file

@ -2,6 +2,9 @@
"id": "reporting",
"version": "8.0.0",
"kibanaVersion": "kibana",
"optionalPlugins": [
"usageCollection"
],
"configPath": ["xpack", "reporting"],
"requiredPlugins": [
"home",
@ -12,6 +15,6 @@
"share",
"kibanaLegacy"
],
"server": false,
"server": true,
"ui": true
}

View file

@ -0,0 +1,165 @@
/*
* 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 * as Rx from 'rxjs';
import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server';
import { ConfigType as ReportingConfigType } from './schema';
import { createConfig$ } from './create_config';
interface KibanaServer {
host?: string;
port?: number;
protocol?: string;
}
const makeMockInitContext = (config: {
capture?: Partial<ReportingConfigType['capture']>;
encryptionKey?: string;
kibanaServer: Partial<ReportingConfigType['kibanaServer']>;
}): PluginInitializerContext =>
({
config: {
create: () =>
Rx.of({
...config,
capture: config.capture || { browser: { chromium: { disableSandbox: false } } },
kibanaServer: config.kibanaServer || {},
}),
},
} as PluginInitializerContext);
const makeMockCoreSetup = (serverInfo: KibanaServer): CoreSetup =>
({ http: { getServerInfo: () => serverInfo } } as any);
describe('Reporting server createConfig$', () => {
let mockCoreSetup: CoreSetup;
let mockInitContext: PluginInitializerContext;
let mockLogger: Logger;
beforeEach(() => {
mockCoreSetup = makeMockCoreSetup({ host: 'kibanaHost', port: 5601, protocol: 'http' });
mockInitContext = makeMockInitContext({
kibanaServer: {},
});
mockLogger = ({ warn: jest.fn(), debug: jest.fn() } as unknown) as Logger;
});
afterEach(() => {
jest.resetAllMocks();
});
it('creates random encryption key and default config using host, protocol, and port from server info', async () => {
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters
expect(result.kibanaServer).toMatchInlineSnapshot(`
Object {
"hostname": "kibanaHost",
"port": 5601,
"protocol": "http",
}
`);
expect((mockLogger.warn as any).mock.calls.length).toBe(1);
expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([
'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in kibana.yml',
]);
});
it('uses the user-provided encryption key', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
kibanaServer: {},
});
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii');
expect((mockLogger.warn as any).mock.calls.length).toBe(0);
});
it('uses the user-provided encryption key, reporting kibanaServer settings to override server info', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
kibanaServer: {
hostname: 'reportingHost',
port: 5677,
protocol: 'httpsa',
},
});
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result).toMatchInlineSnapshot(`
Object {
"capture": Object {
"browser": Object {
"chromium": Object {
"disableSandbox": false,
},
},
},
"encryptionKey": "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii",
"kibanaServer": Object {
"hostname": "reportingHost",
"port": 5677,
"protocol": "httpsa",
},
}
`);
expect((mockLogger.warn as any).mock.calls.length).toBe(0);
});
it('show warning when kibanaServer.hostName === "0"', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa',
kibanaServer: { hostname: '0' },
});
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.kibanaServer).toMatchInlineSnapshot(`
Object {
"hostname": "0.0.0.0",
"port": 5601,
"protocol": "http",
}
`);
expect((mockLogger.warn as any).mock.calls.length).toBe(1);
expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([
`Found 'server.host: \"0\"' in Kibana configuration. This is incompatible with Reporting. To enable Reporting to work, 'xpack.reporting.kibanaServer.hostname: 0.0.0.0' is being automatically ` +
`to the configuration. You can change the setting to 'server.host: 0.0.0.0' or add 'xpack.reporting.kibanaServer.hostname: 0.0.0.0' in kibana.yml to prevent this message.`,
]);
});
it('uses user-provided disableSandbox: false', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: '888888888888888888888888888888888',
capture: { browser: { chromium: { disableSandbox: false } } },
} as ReportingConfigType);
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false });
expect((mockLogger.warn as any).mock.calls.length).toBe(0);
});
it('uses user-provided disableSandbox: true', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: '888888888888888888888888888888888',
capture: { browser: { chromium: { disableSandbox: true } } },
} as ReportingConfigType);
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true });
expect((mockLogger.warn as any).mock.calls.length).toBe(0);
});
it('provides a default for disableSandbox', async () => {
mockInitContext = makeMockInitContext({
encryptionKey: '888888888888888888888888888888888',
} as ReportingConfigType);
const result = await createConfig$(mockCoreSetup, mockInitContext, mockLogger).toPromise();
expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) });
expect((mockLogger.warn as any).mock.calls.length).toBe(0);
});
});

View file

@ -0,0 +1,124 @@
/*
* 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 { i18n } from '@kbn/i18n/';
import { TypeOf } from '@kbn/config-schema';
import crypto from 'crypto';
import { capitalize } from 'lodash';
import { map, mergeMap } from 'rxjs/operators';
import { CoreSetup, Logger, PluginInitializerContext } from 'src/core/server';
import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
import { ConfigSchema } from './schema';
/*
* Set up dynamic config defaults
* - xpack.capture.browser.chromium.disableSandbox
* - xpack.kibanaServer
* - xpack.reporting.encryptionKey
*/
export function createConfig$(core: CoreSetup, context: PluginInitializerContext, logger: Logger) {
return context.config.create<TypeOf<typeof ConfigSchema>>().pipe(
map(config => {
// encryption key
let encryptionKey = config.encryptionKey;
if (encryptionKey === undefined) {
logger.warn(
i18n.translate('xpack.reporting.serverConfig.randomEncryptionKey', {
defaultMessage:
'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' +
'restart, please set xpack.reporting.encryptionKey in kibana.yml',
})
);
encryptionKey = crypto.randomBytes(16).toString('hex');
}
const { kibanaServer: reportingServer } = config;
const serverInfo = core.http.getServerInfo();
// kibanaServer.hostname, default to server.host, don't allow "0"
let kibanaServerHostname = reportingServer.hostname
? reportingServer.hostname
: serverInfo.host;
if (kibanaServerHostname === '0') {
logger.warn(
i18n.translate('xpack.reporting.serverConfig.invalidServerHostname', {
defaultMessage:
`Found 'server.host: "0"' in Kibana configuration. This is incompatible with Reporting. ` +
`To enable Reporting to work, '{configKey}: 0.0.0.0' is being automatically to the configuration. ` +
`You can change the setting to 'server.host: 0.0.0.0' or add '{configKey}: 0.0.0.0' in kibana.yml to prevent this message.`,
values: { configKey: 'xpack.reporting.kibanaServer.hostname' },
})
);
kibanaServerHostname = '0.0.0.0';
}
// kibanaServer.port, default to server.port
const kibanaServerPort = reportingServer.port
? reportingServer.port
: serverInfo.port; // prettier-ignore
// kibanaServer.protocol, default to server.protocol
const kibanaServerProtocol = reportingServer.protocol
? reportingServer.protocol
: serverInfo.protocol;
return {
...config,
encryptionKey,
kibanaServer: {
hostname: kibanaServerHostname,
port: kibanaServerPort,
protocol: kibanaServerProtocol,
},
};
}),
mergeMap(async config => {
if (config.capture.browser.chromium.disableSandbox != null) {
// disableSandbox was set by user
return config;
}
// disableSandbox was not set by user, apply default for OS
const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled();
const osName = [os.os, os.dist, os.release]
.filter(Boolean)
.map(capitalize)
.join(' ');
logger.debug(
i18n.translate('xpack.reporting.serverConfig.osDetected', {
defaultMessage: `Running on OS: '{osName}'`,
values: { osName },
})
);
if (disableSandbox === true) {
logger.warn(
i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxDisabled', {
defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`,
values: {
configKey: 'xpack.reporting.capture.browser.chromium.disableSandbox',
osName,
},
})
);
} else {
logger.info(
i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxEnabled', {
defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`,
values: { osName },
})
);
}
return {
...config,
capture: {
...config.capture,
browser: {
...config.capture.browser,
chromium: { ...config.capture.browser.chromium, disableSandbox },
},
},
};
})
);
}

View file

@ -11,11 +11,17 @@ jest.mock('getos', () => {
import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled';
import getos from 'getos';
function defaultTest(os, expectedDefault) {
interface TestObject {
os: string;
dist?: string;
release?: string;
}
function defaultTest(os: TestObject, expectedDefault: boolean) {
test(`${expectedDefault ? 'disabled' : 'enabled'} on ${JSON.stringify(os)}`, async () => {
getos.mockImplementation(cb => cb(null, os));
(getos as jest.Mock).mockImplementation(cb => cb(null, os));
const actualDefault = await getDefaultChromiumSandboxDisabled();
expect(actualDefault).toBe(expectedDefault);
expect(actualDefault.disableSandbox).toBe(expectedDefault);
});
}

View file

@ -31,12 +31,17 @@ const distroSupportsUnprivilegedUsernamespaces = (distro: string) => {
return true;
};
export async function getDefaultChromiumSandboxDisabled() {
interface OsSummary {
disableSandbox: boolean;
os: { os: string; dist?: string; release?: string };
}
export async function getDefaultChromiumSandboxDisabled(): Promise<OsSummary> {
const os = await getos();
if (os.os === 'linux' && !distroSupportsUnprivilegedUsernamespaces(os.dist)) {
return true;
return { os, disableSandbox: true };
} else {
return false;
return { os, disableSandbox: false };
}
}

View file

@ -0,0 +1,23 @@
/*
* 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 { PluginConfigDescriptor } from 'kibana/server';
import { ConfigSchema, ConfigType } from './schema';
export { createConfig$ } from './create_config';
export const config: PluginConfigDescriptor<ConfigType> = {
schema: ConfigSchema,
deprecations: ({ unused }) => [
unused('capture.browser.chromium.maxScreenshotDimension'),
unused('capture.concurrency'),
unused('capture.settleTime'),
unused('capture.timeout'),
unused('kibanaApp'),
],
};
export { ConfigSchema, ConfigType };

View file

@ -0,0 +1,134 @@
/*
* 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 { ConfigSchema } from './schema';
describe('Reporting Config Schema', () => {
it(`context {"dev":false,"dist":false} produces correct config`, () => {
expect(ConfigSchema.validate({}, { dev: false, dist: false })).toMatchObject({
capture: {
browser: {
autoDownload: true,
chromium: { proxy: { enabled: false } },
type: 'chromium',
},
loadDelay: 3000,
maxAttempts: 1,
networkPolicy: {
enabled: true,
rules: [
{ allow: true, host: undefined, protocol: 'http:' },
{ allow: true, host: undefined, protocol: 'https:' },
{ allow: true, host: undefined, protocol: 'ws:' },
{ allow: true, host: undefined, protocol: 'wss:' },
{ allow: true, host: undefined, protocol: 'data:' },
{ allow: false, host: undefined, protocol: undefined },
],
},
viewport: { height: 1200, width: 1950 },
zoom: 2,
},
csv: {
checkForFormulas: true,
enablePanelActionDownload: true,
maxSizeBytes: 10485760,
scroll: { duration: '30s', size: 500 },
},
encryptionKey: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
index: '.reporting',
kibanaServer: {},
poll: {
jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 },
jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 },
},
queue: {
indexInterval: 'week',
pollEnabled: true,
pollInterval: 3000,
pollIntervalErrorMultiplier: 10,
timeout: 120000,
},
roles: { allow: ['reporting_user'] },
});
});
it(`context {"dev":false,"dist":true} produces correct config`, () => {
expect(ConfigSchema.validate({}, { dev: false, dist: true })).toMatchObject({
capture: {
browser: {
autoDownload: false,
chromium: {
inspect: false,
proxy: { enabled: false },
},
type: 'chromium',
},
loadDelay: 3000,
maxAttempts: 3,
networkPolicy: {
enabled: true,
rules: [
{ allow: true, host: undefined, protocol: 'http:' },
{ allow: true, host: undefined, protocol: 'https:' },
{ allow: true, host: undefined, protocol: 'ws:' },
{ allow: true, host: undefined, protocol: 'wss:' },
{ allow: true, host: undefined, protocol: 'data:' },
{ allow: false, host: undefined, protocol: undefined },
],
},
viewport: { height: 1200, width: 1950 },
zoom: 2,
},
csv: {
checkForFormulas: true,
enablePanelActionDownload: true,
maxSizeBytes: 10485760,
scroll: { duration: '30s', size: 500 },
},
index: '.reporting',
kibanaServer: {},
poll: {
jobCompletionNotifier: { interval: 10000, intervalErrorMultiplier: 5 },
jobsRefresh: { interval: 5000, intervalErrorMultiplier: 5 },
},
queue: {
indexInterval: 'week',
pollEnabled: true,
pollInterval: 3000,
pollIntervalErrorMultiplier: 10,
timeout: 120000,
},
roles: { allow: ['reporting_user'] },
});
});
it(`allows optional settings`, () => {
// encryption key
expect(
ConfigSchema.validate({ encryptionKey: 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' })
.encryptionKey
).toBe('qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq');
// disableSandbox
expect(
ConfigSchema.validate({ capture: { browser: { chromium: { disableSandbox: true } } } })
.capture.browser.chromium
).toMatchObject({ disableSandbox: true, proxy: { enabled: false } });
// kibanaServer
expect(
ConfigSchema.validate({ kibanaServer: { hostname: 'Frodo' } }).kibanaServer
).toMatchObject({ hostname: 'Frodo' });
});
it(`logs the proper validation messages`, () => {
// kibanaServer
const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } });
expect(throwValidationErr).toThrowError(
`[kibanaServer.hostname]: must not be "0" for the headless browser to correctly resolve the host`
);
});
});

View file

@ -0,0 +1,174 @@
/*
* 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 { schema, TypeOf } from '@kbn/config-schema';
import moment from 'moment';
const KibanaServerSchema = schema.object({
hostname: schema.maybe(
schema.string({
validate(value) {
if (value === '0') {
return 'must not be "0" for the headless browser to correctly resolve the host';
}
},
hostname: true,
})
),
port: schema.maybe(schema.number()),
protocol: schema.maybe(
schema.string({
validate(value) {
if (!/^https?$/.test(value)) {
return 'must be "http" or "https"';
}
},
})
),
}); // default values are all dynamic in createConfig$
const QueueSchema = schema.object({
indexInterval: schema.string({ defaultValue: 'week' }),
pollEnabled: schema.boolean({ defaultValue: true }),
pollInterval: schema.number({ defaultValue: 3000 }),
pollIntervalErrorMultiplier: schema.number({ defaultValue: 10 }),
timeout: schema.number({ defaultValue: moment.duration(2, 'm').asMilliseconds() }),
});
const RulesSchema = schema.object({
allow: schema.boolean(),
host: schema.maybe(schema.string()),
protocol: schema.maybe(schema.string()),
});
const CaptureSchema = schema.object({
timeouts: schema.object({
openUrl: schema.number({ defaultValue: 30000 }),
waitForElements: schema.number({ defaultValue: 30000 }),
renderComplete: schema.number({ defaultValue: 30000 }),
}),
networkPolicy: schema.object({
enabled: schema.boolean({ defaultValue: true }),
rules: schema.arrayOf(RulesSchema, {
defaultValue: [
{ host: undefined, allow: true, protocol: 'http:' },
{ host: undefined, allow: true, protocol: 'https:' },
{ host: undefined, allow: true, protocol: 'ws:' },
{ host: undefined, allow: true, protocol: 'wss:' },
{ host: undefined, allow: true, protocol: 'data:' },
{ host: undefined, allow: false, protocol: undefined }, // Default action is to deny!
],
}),
}),
zoom: schema.number({ defaultValue: 2 }),
viewport: schema.object({
width: schema.number({ defaultValue: 1950 }),
height: schema.number({ defaultValue: 1200 }),
}),
loadDelay: schema.number({
defaultValue: moment.duration(3, 's').asMilliseconds(),
}), // TODO: use schema.duration
browser: schema.object({
autoDownload: schema.conditional(
schema.contextRef('dist'),
true,
schema.boolean({ defaultValue: false }),
schema.boolean({ defaultValue: true })
),
chromium: schema.object({
inspect: schema.conditional(
schema.contextRef('dist'),
true,
schema.boolean({ defaultValue: false }),
schema.maybe(schema.never())
),
disableSandbox: schema.maybe(schema.boolean()), // default value is dynamic in createConfig$
proxy: schema.object({
enabled: schema.boolean({ defaultValue: false }),
server: schema.conditional(
schema.siblingRef('enabled'),
true,
schema.uri({ scheme: ['http', 'https'] }),
schema.maybe(schema.never())
),
bypass: schema.conditional(
schema.siblingRef('enabled'),
true,
schema.arrayOf(schema.string({ hostname: true })),
schema.maybe(schema.never())
),
}),
}),
type: schema.string({ defaultValue: 'chromium' }),
}),
maxAttempts: schema.conditional(
schema.contextRef('dist'),
true,
schema.number({ defaultValue: 3 }),
schema.number({ defaultValue: 1 })
),
});
const CsvSchema = schema.object({
checkForFormulas: schema.boolean({ defaultValue: true }),
enablePanelActionDownload: schema.boolean({ defaultValue: true }),
maxSizeBytes: schema.number({
defaultValue: 1024 * 1024 * 10, // 10MB
}), // TODO: use schema.byteSize
useByteOrderMarkEncoding: schema.boolean({ defaultValue: false }),
scroll: schema.object({
duration: schema.string({
defaultValue: '30s',
validate(value) {
if (!/^[0-9]+(d|h|m|s|ms|micros|nanos)$/.test(value)) {
return 'must be a duration string';
}
},
}),
size: schema.number({ defaultValue: 500 }),
}),
});
const EncryptionKeySchema = schema.conditional(
schema.contextRef('dist'),
true,
schema.maybe(schema.string({ minLength: 32 })), // default value is dynamic in createConfig$
schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) })
);
const RolesSchema = schema.object({
allow: schema.arrayOf(schema.string(), { defaultValue: ['reporting_user'] }),
});
const IndexSchema = schema.string({ defaultValue: '.reporting' });
const PollSchema = schema.object({
jobCompletionNotifier: schema.object({
interval: schema.number({
defaultValue: moment.duration(10, 's').asMilliseconds(),
}), // TODO: use schema.duration
intervalErrorMultiplier: schema.number({ defaultValue: 5 }),
}),
jobsRefresh: schema.object({
interval: schema.number({
defaultValue: moment.duration(5, 's').asMilliseconds(),
}), // TODO: use schema.duration
intervalErrorMultiplier: schema.number({ defaultValue: 5 }),
}),
});
export const ConfigSchema = schema.object({
kibanaServer: KibanaServerSchema,
queue: QueueSchema,
capture: CaptureSchema,
csv: CsvSchema,
encryptionKey: EncryptionKeySchema,
roles: RolesSchema,
index: IndexSchema,
poll: PollSchema,
});
export type ConfigType = TypeOf<typeof ConfigSchema>;

View file

@ -0,0 +1,14 @@
/*
* 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 { PluginInitializerContext } from 'src/core/server';
import { ReportingPlugin } from './plugin';
export { config, ConfigSchema } from './config';
export { ConfigType, PluginsSetup } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) =>
new ReportingPlugin(initializerContext);

View file

@ -0,0 +1,38 @@
/*
* 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 { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server';
import { ConfigType, createConfig$ } from './config';
export interface PluginsSetup {
/** @deprecated */
__legacy: {
config$: Observable<ConfigType>;
};
}
export class ReportingPlugin implements Plugin<PluginsSetup> {
private readonly log: Logger;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.log = this.initializerContext.logger.get();
}
public async setup(core: CoreSetup): Promise<PluginsSetup> {
return {
__legacy: {
config$: createConfig$(core, this.initializerContext, this.log).pipe(first()),
},
};
}
public start() {}
public stop() {}
}
export { ConfigType };

View file

@ -12431,7 +12431,6 @@
"xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化",
"xpack.reporting.selfCheck.ok": "レポートプラグイン自己チェックOK!",
"xpack.reporting.selfCheck.warning": "レポートプラグイン自己チェックで警告が発生しました: {err}",
"xpack.reporting.selfCheckEncryptionKey.warning": "{setting}のランダムキーを生成しています。保留中のレポートの再開が失敗しないように、kibana.ymlで{setting}を設定してください",
"xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート",
"xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート",
"xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート",

View file

@ -12435,7 +12435,6 @@
"xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化",
"xpack.reporting.selfCheck.ok": "Reporting 插件自检正常!",
"xpack.reporting.selfCheck.warning": "Reporting 插件自检生成警告:{err}",
"xpack.reporting.selfCheckEncryptionKey.warning": "正在为 {setting} 生成随机密钥。要防止待处理报告在重新启动时失败,请在 kibana.yml 中设置 {setting}",
"xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告",
"xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告",
"xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告",