diff --git a/x-pack/package.json b/x-pack/package.json index 2c741c83ea987..2ae1487e74512 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -86,6 +86,7 @@ "gulp-mocha": "2.2.0", "gulp-multi-process": "^1.3.1", "hapi": "^17.5.3", + "is-path-inside": "^2.0.0", "jest": "^23.6.0", "jest-cli": "^23.6.0", "jest-styled-components": "^6.2.2", diff --git a/x-pack/plugins/canvas/tasks/helpers/import_whitelist_plugin.js b/x-pack/plugins/canvas/tasks/helpers/import_whitelist_plugin.js new file mode 100644 index 0000000000000..6a13e4763bd8c --- /dev/null +++ b/x-pack/plugins/canvas/tasks/helpers/import_whitelist_plugin.js @@ -0,0 +1,49 @@ +/* + * 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 { relative, resolve } from 'path'; + +import isPathInside from 'is-path-inside'; +import chalk from 'chalk'; + +const KIBANA_ROOT = resolve(__dirname, '../../../../..'); + +function match(path, pathsOrRegexps) { + return [].concat(pathsOrRegexps).some(pathOrRegexp => { + if (pathOrRegexp instanceof RegExp) return pathOrRegexp.test(path); + return path === pathOrRegexp || isPathInside(path, pathOrRegexp); + }); +} + +export class ImportWhitelistPlugin { + constructor({ from, whitelist }) { + this.from = from; + this.whitelist = whitelist; + } + + apply(resolver) { + resolver.hooks.file.tap('ImportWhitelistPlugin', request => { + if (!request.context || !request.context.issuer) { + // ignore internal requests that don't have an issuer + return; + } + + if (!match(request.context.issuer, this.from)) { + // request is not filtered by this whitelist unless it comes + // from a module matching the from directory/regex + return; + } + + if (!match(request.path, this.whitelist)) { + throw new Error( + `Attempted to import "${chalk.yellow(relative(KIBANA_ROOT, request.path))}" which ` + + `is not in the whitelist for the ${chalk.cyan(relative(KIBANA_ROOT, this.from))} ` + + `directory.` + ); + } + }); + } +} diff --git a/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js b/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js index e87ee70bdc9cc..1fa9723f50abd 100644 --- a/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js +++ b/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); +import path from 'path'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; + +import { ImportWhitelistPlugin } from './import_whitelist_plugin'; const sourceDir = path.resolve(__dirname, '../../canvas_plugin_src'); const buildDir = path.resolve(__dirname, '../../canvas_plugin'); +const sharedDir = path.resolve(__dirname, '../../shared'); export function getWebpackConfig({ devtool, watch } = {}) { return { @@ -54,6 +57,23 @@ export function getWebpackConfig({ devtool, watch } = {}) { resolve: { extensions: ['.ts', '.tsx', '.js', '.json'], mainFields: ['browser', 'main'], + plugins: [ + // whitelist imports from the canvas_plugin_src directory + // to ensure that only modules in its own directory and node_modules + // are included in the canvas_plugin bundle. An additional /shared/ + // directory at the root of canvas is included so that code can be shared + // with the rest of the canvas app + new ImportWhitelistPlugin({ + from: sourceDir, + whitelist: [/[\/\\]node_modules[\/\\]/, sourceDir, sharedDir], + }), + // only whitelist node_modules and code within the shared directory so that + // they can safely be imported by the canvas_plugin_src or the canvas app + new ImportWhitelistPlugin({ + from: sharedDir, + whitelist: [/[\/\\]node_modules[\/\\]/, sharedDir], + }), + ], }, plugins: [ diff --git a/yarn.lock b/yarn.lock index bdd2e1a79c886..2445496c5c353 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11511,6 +11511,13 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-path-inside@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.0.0.tgz#28f249020fe6906671fe31a76118f2cd490441fd" + integrity sha512-OmUXvSq+P7aI/aRbl1dzwdlyLn8vW7Nr2/11S7y/dcLLgnQ89hgYJp7tfc+A5SRid3rNCLpruOp2CAV68/iOcA== + dependencies: + path-is-inside "^1.0.2" + is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"