Allow Vega's emsfile to bypass sanitization (#17370)

* Allow Vega's emsfile to bypass sanitization

Fixes https://github.com/elastic/kibana/issues/16669
This commit is contained in:
Yuri Astrakhan 2018-04-02 20:01:23 +03:00 committed by GitHub
parent 49fae1d599
commit 1b4a7d1aaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 16 deletions

View file

@ -1,6 +1,7 @@
import _ from 'lodash';
import expect from 'expect.js';
import { VegaParser } from '../vega_parser';
import { bypassExternalUrlCheck } from '../../vega_view/vega_base_view';
describe(`VegaParser._setDefaultValue`, () => {
@ -62,6 +63,7 @@ describe('VegaParser._resolveEsQueries', () => {
getFileLayers: async () => [{ name: 'file1', url: 'url1' }]
});
await vp._resolveDataUrls();
expect(vp.spec).to.eql(expected);
expect(vp.warnings).to.have.length(warnCount || 0);
};
@ -73,7 +75,9 @@ describe('VegaParser._resolveEsQueries', () => {
it('es', test({ data: { url: { index: 'a' }, x: 1 } }, { data: { values: [42], x: 1 } }));
it('es', test({ data: { url: { '%type%': 'elasticsearch', index: 'a' } } }, { data: { values: [42] } }));
it('es arr', test({ arr: [{ data: { url: { index: 'a' }, x: 1 } }] }, { arr: [{ data: { values: [42], x: 1 } }] }));
it('emsfile', test({ data: { url: { '%type%': 'emsfile', name: 'file1' } } }, { data: { url: 'url1' } }));
it('emsfile', test(
{ data: { url: { '%type%': 'emsfile', name: 'file1' } } },
{ data: { url: bypassExternalUrlCheck('url1') } }));
});
describe('VegaParser._parseSchema', () => {

View file

@ -1,3 +1,5 @@
import { bypassExternalUrlCheck } from '../vega_view/vega_base_view';
/**
* This class processes all Vega spec customizations,
* converting url object parameters into query results.
@ -38,7 +40,9 @@ export class EmsFileParser {
if (!foundLayer) {
throw new Error(`emsfile ${JSON.stringify(name)} does not exist`);
}
obj.url = foundLayer.url;
// This URL can bypass loader sanitization at the later stage
obj.url = bypassExternalUrlCheck(foundLayer.url);
}
}

View file

@ -8,6 +8,13 @@ vega.scheme('elastic',
['#00B3A4', '#3185FC', '#DB1374', '#490092', '#FEB6DB', '#F98510', '#E6C220', '#BFA180', '#920000', '#461A0A']
);
const bypassToken = Symbol();
export function bypassExternalUrlCheck(url) {
// processed in the loader.sanitize below
return { url, bypassToken };
}
export class VegaBaseView {
constructor(vegaConfig, editorMode, parentEl, vegaParser, serviceSettings) {
this._vegaConfig = vegaConfig;
@ -58,20 +65,7 @@ export class VegaBaseView {
}
});
this._vegaViewConfig = {
logLevel: vega.Warn,
renderer: this._parser.renderer,
};
if (!this._vegaConfig.enableExternalUrls) {
// Override URL loader and sanitizer to disable all URL-based requests
const errorFunc = () => {
throw new Error('External URLs are not enabled. Add "vega": {"enableExternalUrls": true} to kibana.yml');
};
const loader = vega.loader();
loader.load = errorFunc;
loader.sanitize = errorFunc;
this._vegaViewConfig.loader = loader;
}
this._vegaViewConfig = this.createViewConfig();
// The derived class should create this method
await this._initViewCustomizations();
@ -80,6 +74,30 @@ export class VegaBaseView {
}
}
createViewConfig() {
const config = {
logLevel: vega.Warn,
renderer: this._parser.renderer,
};
// Override URL sanitizer to prevent external data loading (if disabled)
const loader = vega.loader();
const originalSanitize = loader.sanitize.bind(loader);
loader.sanitize = (uri, options) => {
if (uri.bypassToken === bypassToken) {
// If uri has a bypass token, the uri was encoded by bypassExternalUrlCheck() above.
// because user can only supply pure JSON data structure.
uri = uri.url;
} else if (!this._vegaConfig.enableExternalUrls) {
throw new Error('External URLs are not enabled. Add vega.enableExternalUrls: true to kibana.yml');
}
return originalSanitize(uri, options);
};
config.loader = loader;
return config;
}
onError() {
this._addMessage('err', Utils.formatErrorToStr(...arguments));
}