From 16694e2b4f4929db17279d06c9d6a1bc48f96747 Mon Sep 17 00:00:00 2001 From: Anan <79961084+ananzh@users.noreply.github.com> Date: Fri, 4 Jun 2021 15:08:02 -0700 Subject: [PATCH] [Patch] Graphite SSRF patch (#392) * ssrf patch Signed-off-by: Anan Zhuang * revise based on PR comments Signed-off-by: Anan Zhuang * revise unit test and comments Signed-off-by: Anan Zhuang * fix lint issue Signed-off-by: Anan Zhuang * add helper Signed-off-by: Anan Zhuang * fix bug Signed-off-by: Anan Zhuang * fix comments Signed-off-by: Anan Zhuang * fix frontend display. helper shouldnot show in visualize Signed-off-by: Anan Zhuang --- config/opensearch_dashboards.yml | 35 +++++ package.json | 2 + src/plugins/vis_type_timeline/config.ts | 3 +- src/plugins/vis_type_timeline/server/index.ts | 1 + .../server/lib/config_manager.ts | 14 +- .../vis_type_timeline/server/plugin.ts | 10 +- .../vis_type_timeline/server/routes/run.ts | 3 +- .../series_functions/fixtures/tl_config.js | 5 + .../server/series_functions/graphite.js | 30 +++- .../server/series_functions/graphite.test.js | 142 +++++++++++++++++- .../helpers/graphite_helper.js | 97 ++++++++++++ .../helpers/graphite_helper.test.js | 115 ++++++++++++++ yarn.lock | 79 +++++++++- 13 files changed, 512 insertions(+), 24 deletions(-) create mode 100644 src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.js create mode 100644 src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.test.js diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 5b395fd0298c..7c83ba0df5d4 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -105,3 +105,38 @@ # Specifies locale to be used for all localizable strings, dates and number formats. # Supported languages are the following: English - en , by default , Chinese - zh-CN . #i18n.locale: "en" + +# Set the allowlist to check input graphite Url. Allowlist is the default check list. +#vis_type_timeline.graphiteAllowedUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'] + +# Set the blocklist to check input graphite Url. Blocklist is an IP list. +# Below is an example for reference +# vis_type_timeline.graphiteBlockedIPs: [ +# //Loopback +# '127.0.0.0/8', +# '::1/128', +# //Link-local Address for IPv6 +# 'fe80::/10', +# //Private IP address for IPv4 +# '10.0.0.0/8', +# '172.16.0.0/12', +# '192.168.0.0/16', +# //Unique local address (ULA) +# 'fc00::/7', +# //Reserved IP address +# '0.0.0.0/8', +# '100.64.0.0/10', +# '192.0.0.0/24', +# '192.0.2.0/24', +# '198.18.0.0/15', +# '192.88.99.0/24', +# '198.51.100.0/24', +# '203.0.113.0/24', +# '224.0.0.0/4', +# '240.0.0.0/4', +# '255.255.255.255/32', +# '::/128', +# '2001:db8::/32', +# 'ff00::/8', +# ] +#vis_type_timeline.graphiteBlockedIPs: [] diff --git a/package.json b/package.json index 00e78f7f2aac..7b005994ca70 100644 --- a/package.json +++ b/package.json @@ -149,6 +149,7 @@ "cypress-promise": "^1.1.0", "deep-freeze-strict": "^1.1.1", "del": "^5.1.0", + "dns-sync": "^0.2.1", "elastic-apm-node": "^3.7.0", "elasticsearch": "^16.7.0", "execa": "^4.0.2", @@ -169,6 +170,7 @@ "https-proxy-agent": "^5.0.0", "inert": "^5.1.0", "inline-style": "^2.0.0", + "ip-cidr": "^2.1.0", "joi": "^13.5.2", "js-yaml": "^3.14.0", "json-stable-stringify": "^1.0.1", diff --git a/src/plugins/vis_type_timeline/config.ts b/src/plugins/vis_type_timeline/config.ts index c8fcd146fb12..71765da00699 100644 --- a/src/plugins/vis_type_timeline/config.ts +++ b/src/plugins/vis_type_timeline/config.ts @@ -36,7 +36,8 @@ export const configSchema = schema.object( { enabled: schema.boolean({ defaultValue: true }), ui: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), - graphiteUrls: schema.maybe(schema.arrayOf(schema.string())), + graphiteAllowedUrls: schema.maybe(schema.arrayOf(schema.string())), + graphiteBlockedIPs: schema.maybe(schema.arrayOf(schema.string())), }, // This option should be removed as soon as we entirely migrate config from legacy Timeline plugin. { unknowns: 'allow' } diff --git a/src/plugins/vis_type_timeline/server/index.ts b/src/plugins/vis_type_timeline/server/index.ts index a843ceccee97..a0e8ec745b15 100644 --- a/src/plugins/vis_type_timeline/server/index.ts +++ b/src/plugins/vis_type_timeline/server/index.ts @@ -45,6 +45,7 @@ export const config: PluginConfigDescriptor = { renameFromRoot('timeline_vis.enabled', 'vis_type_timeline.enabled'), renameFromRoot('timeline.enabled', 'vis_type_timeline.enabled'), renameFromRoot('timeline.graphiteUrls', 'vis_type_timeline.graphiteUrls'), + renameFromRoot('vis_type_timeline.graphiteUrls', 'vis_type_timeline.graphiteAllowedUrls'), renameFromRoot('timeline.ui.enabled', 'vis_type_timeline.ui.enabled', true), ], }; diff --git a/src/plugins/vis_type_timeline/server/lib/config_manager.ts b/src/plugins/vis_type_timeline/server/lib/config_manager.ts index 2b9acb04d0fe..c3beb812295b 100644 --- a/src/plugins/vis_type_timeline/server/lib/config_manager.ts +++ b/src/plugins/vis_type_timeline/server/lib/config_manager.ts @@ -36,11 +36,13 @@ import { configSchema } from '../../config'; export class ConfigManager { private opensearchShardTimeout: number = 0; - private graphiteUrls: string[] = []; + private graphiteAllowedUrls: string[] = []; + private graphiteBlockedIPs: string[] = []; constructor(config: PluginInitializerContext['config']) { config.create>().subscribe((configUpdate) => { - this.graphiteUrls = configUpdate.graphiteUrls || []; + this.graphiteAllowedUrls = configUpdate.graphiteAllowedUrls || []; + this.graphiteBlockedIPs = configUpdate.graphiteBlockedIPs || []; }); config.legacy.globalConfig$.subscribe((configUpdate) => { @@ -52,7 +54,11 @@ export class ConfigManager { return this.opensearchShardTimeout; } - getGraphiteUrls() { - return this.graphiteUrls; + getGraphiteAllowedUrls() { + return this.graphiteAllowedUrls; + } + + getGraphiteBlockedIPs() { + return this.graphiteBlockedIPs; } } diff --git a/src/plugins/vis_type_timeline/server/plugin.ts b/src/plugins/vis_type_timeline/server/plugin.ts index ac507d6235c0..2a271d2f5124 100644 --- a/src/plugins/vis_type_timeline/server/plugin.ts +++ b/src/plugins/vis_type_timeline/server/plugin.ts @@ -171,14 +171,14 @@ export class Plugin { description: 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', }), - value: config.graphiteUrls && config.graphiteUrls.length ? config.graphiteUrls[0] : null, + value: + config.graphiteAllowedUrls && config.graphiteAllowedUrls.length + ? config.graphiteAllowedUrls[0] + : null, description: i18n.translate('timeline.uiSettings.graphiteURLDescription', { - defaultMessage: - '{experimentalLabel} The URL of your graphite host', + defaultMessage: '{experimentalLabel} The URL of your graphite host', values: { experimentalLabel: `[${experimentalLabel}]` }, }), - type: 'select', - options: config.graphiteUrls || [], category: ['timeline'], schema: schema.nullable(schema.string()), }, diff --git a/src/plugins/vis_type_timeline/server/routes/run.ts b/src/plugins/vis_type_timeline/server/routes/run.ts index 4e470a069d73..d892aa0f300b 100644 --- a/src/plugins/vis_type_timeline/server/routes/run.ts +++ b/src/plugins/vis_type_timeline/server/routes/run.ts @@ -101,7 +101,8 @@ export function runRoute( settings: _.defaults(uiSettings, timelineDefaults), // Just in case they delete some setting. getFunction, getStartServices: core.getStartServices, - allowedGraphiteUrls: configManager.getGraphiteUrls(), + allowedGraphiteUrls: configManager.getGraphiteAllowedUrls(), + blockedGraphiteIPs: configManager.getGraphiteBlockedIPs(), opensearchShardTimeout: configManager.getOpenSearchShardTimeout(), savedObjectsClient: context.core.savedObjects.client, }); diff --git a/src/plugins/vis_type_timeline/server/series_functions/fixtures/tl_config.js b/src/plugins/vis_type_timeline/server/series_functions/fixtures/tl_config.js index a404457904df..611cddc8f347 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/fixtures/tl_config.js +++ b/src/plugins/vis_type_timeline/server/series_functions/fixtures/tl_config.js @@ -56,6 +56,7 @@ export default function () { opensearchShardTimeout: moment.duration(30000), allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], }); tlConfig.time = { @@ -67,6 +68,10 @@ export default function () { tlConfig.settings = timelineDefaults(); + tlConfig.allowedGraphiteUrls = timelineDefaults(); + + tlConfig.blockedGraphiteIPs = timelineDefaults(); + tlConfig.setTargetSeries(); return tlConfig; diff --git a/src/plugins/vis_type_timeline/server/series_functions/graphite.js b/src/plugins/vis_type_timeline/server/series_functions/graphite.js index c70ec5e8479a..75a42717a13a 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/graphite.js +++ b/src/plugins/vis_type_timeline/server/series_functions/graphite.js @@ -35,6 +35,13 @@ import _ from 'lodash'; import fetch from 'node-fetch'; import moment from 'moment'; import Datasource from '../lib/classes/datasource'; +import { isValidConfig } from './helpers/graphite_helper'; + +const MISS_CHECKLIST_MESSAGE = `Please configure on the opensearch_dashboards.yml file. +You can always enable the default allowlist configuration.`; + +const INVALID_URL_MESSAGE = `The Graphite URL provided by you is invalid. +Please update your config from OpenSearch Dashboards's Advanced Setting.`; export default new Datasource('graphite', { args: [ @@ -59,19 +66,28 @@ export default new Datasource('graphite', { min: moment(tlConfig.time.from).format('HH:mm[_]YYYYMMDD'), max: moment(tlConfig.time.to).format('HH:mm[_]YYYYMMDD'), }; + const allowedUrls = tlConfig.allowedGraphiteUrls; + const blockedIPs = tlConfig.blockedGraphiteIPs; const configuredUrl = tlConfig.settings['timeline:graphite.url']; - if (!allowedUrls.includes(configuredUrl)) { + + if (allowedUrls.length === 0 && blockedIPs.length === 0) { + throw new Error( + i18n.translate('timeline.help.functions.missCheckGraphiteConfig', { + defaultMessage: MISS_CHECKLIST_MESSAGE, + }) + ); + } + + if (!isValidConfig(blockedIPs, allowedUrls, configuredUrl)) { throw new Error( - i18n.translate('timeline.help.functions.notAllowedGraphiteUrl', { - defaultMessage: `This graphite URL is not configured on the opensearch_dashbpards.yml file. - Please configure your graphite server list in the opensearch_dashbpards.yml file under 'timeline.graphiteUrls' and - select one from OpenSearch Dashboards's Advanced Settings`, + i18n.translate('timeline.help.functions.invalidGraphiteConfig', { + defaultMessage: INVALID_URL_MESSAGE, }) ); } - const URL = + const GRAPHITE_URL = tlConfig.settings['timeline:graphite.url'] + '/render/' + '?format=json' + @@ -82,7 +98,7 @@ export default new Datasource('graphite', { '&target=' + config.metric; - return fetch(URL) + return fetch(GRAPHITE_URL, { redirect: 'error' }) .then(function (resp) { return resp.json(); }) diff --git a/src/plugins/vis_type_timeline/server/series_functions/graphite.test.js b/src/plugins/vis_type_timeline/server/series_functions/graphite.test.js index 8085c8a6082b..c03ca7b0d3d5 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/graphite.test.js +++ b/src/plugins/vis_type_timeline/server/series_functions/graphite.test.js @@ -34,7 +34,16 @@ const expect = require('chai').expect; import fn from './graphite'; -jest.mock('node-fetch', () => () => { +const MISS_CHECKLIST_MESSAGE = `Please configure on the opensearch_dashboards.yml file. +You can always enable the default allowlist configuration.`; + +const INVALID_URL_MESSAGE = `The Graphite URL provided by you is invalid. +Please update your config from OpenSearch Dashboards's Advanced Setting.`; + +jest.mock('node-fetch', () => (url) => { + if (url.includes('redirect')) { + return Promise.reject(new Error('maximum redirect reached at: ' + url)); + } return Promise.resolve({ json: function () { return [ @@ -56,21 +65,146 @@ import invoke from './helpers/invoke_series_fn.js'; describe('graphite', function () { it('should wrap the graphite response up in a seriesList', function () { - return invoke(fn, []).then(function (result) { + return invoke(fn, [], { + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], + }).then(function (result) { expect(result.output.list[0].data[0][1]).to.eql(3); expect(result.output.list[0].data[1][1]).to.eql(14); }); }); it('should convert the seconds to milliseconds', function () { - return invoke(fn, []).then(function (result) { + return invoke(fn, [], { + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], + }).then(function (result) { expect(result.output.list[0].data[1][0]).to.eql(2000 * 1000); }); }); it('should set the label to that of the graphite target', function () { - return invoke(fn, []).then(function (result) { + return invoke(fn, [], { + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], + }).then(function (result) { expect(result.output.list[0].label).to.eql('__beer__'); }); }); + + it('should return error message if both allowlist and blocklist are disabled', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'http://127.0.0.1' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: [], + }).catch((e) => { + expect(e.message).to.eql(MISS_CHECKLIST_MESSAGE); + }); + }); + + it('setting with matched allowlist url should return result', function () { + return invoke(fn, [], { + settings: { + 'timeline:graphite.url': 'https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', + }, + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], + }).then((result) => { + expect(result.output.list.length).to.eql(1); + }); + }); + + it('setting with unmatched allowlist url should return error message', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'http://127.0.0.1' }, + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: [], + }).catch((e) => { + expect(e.message).to.eql(INVALID_URL_MESSAGE); + }); + }); + + it('setting with matched blocklist url should return error message', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'http://127.0.0.1' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).catch((e) => { + expect(e.message).to.eql(INVALID_URL_MESSAGE); + }); + }); + + it('setting with matched blocklist localhost should return error message', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'http://localhost' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).catch((e) => { + expect(e.message).to.eql(INVALID_URL_MESSAGE); + }); + }); + + it('setting with unmatched blocklist https url should return result', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'https://www.opensearch.org/' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).then((result) => { + expect(result.output.list.length).to.eql(1); + }); + }); + + it('setting with unmatched blocklist ftp url should return result', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'ftp://www.opensearch.org' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).then((result) => { + expect(result.output.list.length).to.eql(1); + }); + }); + + it('setting with invalid url should return error message', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'www.opensearch.org' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).catch((e) => { + expect(e.message).to.eql(INVALID_URL_MESSAGE); + }); + }); + + it('setting with redirection error message', function () { + return invoke(fn, [], { + settings: { 'timeline:graphite.url': 'https://www.opensearch.org/redirect' }, + allowedGraphiteUrls: [], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).catch((e) => { + expect(e.message).to.includes('maximum redirect reached'); + }); + }); + + it('with both allowlist and blocklist, setting not in blocklist but in allowlist should return result', function () { + return invoke(fn, [], { + settings: { + 'timeline:graphite.url': 'https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', + }, + allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).then((result) => { + expect(result.output.list.length).to.eql(1); + }); + }); + + it('with conflict allowlist and blocklist, setting in blocklist and in allowlist should return error message', function () { + return invoke(fn, [], { + settings: { + 'timeline:graphite.url': 'http://127.0.0.1', + }, + allowedGraphiteUrls: ['http://127.0.0.1'], + blockedGraphiteIPs: ['127.0.0.0/8'], + }).catch((e) => { + expect(e.message).to.eql(INVALID_URL_MESSAGE); + }); + }); }); diff --git a/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.js b/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.js new file mode 100644 index 000000000000..d62199be7a7d --- /dev/null +++ b/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.js @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +import dns from 'dns-sync'; +import IPCIDR from 'ip-cidr'; +/** + * Resolve hostname to IP address + * @param {object} urlObject + * @returns {string} configuredIP + * or null if it cannot be resolve + * According to RFC, all IPv6 IP address needs to be in [] + * such as [::1] + * So if we detect a IPv6 address, we remove brackets + */ +function getIpAddress(urlObject) { + const hostname = urlObject.hostname; + const configuredIP = dns.resolve(hostname); + if (configuredIP) { + return configuredIP; + } + if (hostname.startsWith('[') && hostname.endsWith(']')) { + return hostname.substr(1).slice(0, -1); + } + return null; +} +/** + * Check whether customer input URL is blocked + * This function first check the format of URL, URL has be in the format as + * scheme://server/path/resource otherwise an TypeError would be thrown + * Then IPCIDR check if a specific IP address fall in the + * range of an IP address block + * @param {string} configuredUrls + * @param {Array|string} blockedIPs + * @returns {boolean} true if the configuredUrl is blocked + */ +function isBlockedURL(configuredUrl, blockedIPs) { + let configuredUrlObject; + try { + configuredUrlObject = new URL(configuredUrl); + } catch (err) { + return true; + } + const ip = exports.getIpAddress(configuredUrlObject); + if (!ip) { + return true; + } + const isBlocked = blockedIPs.some((blockedIP) => new IPCIDR(blockedIP).contains(ip)); + return isBlocked; +} +/** + * Check configured url using blocklist and allowlist + * If allowlist is used, return false if allowlist does not contain configured url + * If blocklist is used, return false if blocklist contains configured url + * If both allowlist and blocklist are used, check blocklist first then allowlist + * @param {Array|string} blockedIPs + * @param {Array|string} allowedUrls + * @param {string} configuredUrls + * @returns {boolean} true if the configuredUrl is valid + */ +function isValidConfig(blockedIPs, allowedUrls, configuredUrl) { + if (blockedIPs.length === 0) { + if (!allowedUrls.includes(configuredUrl)) return false; + } else if (allowedUrls.length === 0) { + if (exports.isBlockedURL(configuredUrl, blockedIPs)) return false; + } else { + if (exports.isBlockedURL(configuredUrl, blockedIPs) || !allowedUrls.includes(configuredUrl)) + return false; + } + return true; +} +export { getIpAddress, isBlockedURL, isValidConfig }; diff --git a/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.test.js b/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.test.js new file mode 100644 index 000000000000..bbb7f0d163a1 --- /dev/null +++ b/src/plugins/vis_type_timeline/server/series_functions/helpers/graphite_helper.test.js @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import * as helper from './graphite_helper'; + +describe('graphite_helper', function () { + it('valid Url should not be blocked and isBlockedURL should return false', function () { + expect(helper.isBlockedURL('https://www.opensearch.org', ['127.0.0.0/8'])).toEqual(false); + }); + + it('blocked Url should be blocked and isBlockedURL should return true', function () { + expect(helper.isBlockedURL('https://127.0.0.1', ['127.0.0.0/8'])).toEqual(true); + }); + + it('invalid Url should be blocked and isBlockedURL should return true', function () { + expect(helper.isBlockedURL('www.opensearch.org', ['127.0.0.0/8'])).toEqual(true); + }); + + it('blocklist should be checked if blocklist is enabled', function () { + jest.spyOn(helper, 'isBlockedURL').mockReturnValueOnce(false); + helper.isValidConfig(['127.0.0.0/8'], [], 'https://www.opensearch.org'); + expect(helper.isBlockedURL).toBeCalled(); + }); + + it('blocklist should be checked it both allowlist and blocklist are enabled', function () { + jest.spyOn(helper, 'isBlockedURL').mockReturnValueOnce(false); + helper.isValidConfig( + ['127.0.0.0/8'], + ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + 'https://www.opensearch.org' + ); + expect(helper.isBlockedURL).toBeCalled(); + }); + + it('with only allowlist, isValidConfig should return false for Url not in the allowlist', function () { + expect( + helper.isValidConfig( + [], + ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + 'https://www.opensearch.org' + ) + ).toEqual(false); + }); + + it('with only allowlist, isValidConfig should return true for Url in the allowlist', function () { + expect( + helper.isValidConfig( + [], + ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + 'https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite' + ) + ).toEqual(true); + }); + + it('with only blocklist, isValidConfig should return false for Url in the blocklist', function () { + expect(helper.isValidConfig(['127.0.0.0/8'], [], 'https://127.0.0.1')).toEqual(false); + }); + + it('with only blocklist, isValidConfig should return true for Url not in the blocklist', function () { + expect(helper.isValidConfig(['127.0.0.0/8'], [], 'https://www.opensearch.org')).toEqual(true); + }); + + it('with both blocklist and allowlist, isValidConfig should return false if allowlist check fails', function () { + expect( + helper.isValidConfig( + ['127.0.0.0/8'], + ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + 'https://www.opensearch.org' + ) + ).toEqual(false); + }); + + it('with both blocklist and allowlist, isValidConfig should return false if blocklist check fails', function () { + expect( + helper.isValidConfig( + ['127.0.0.0/8'], + ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + 'https://127.0.0.1' + ) + ).toEqual(false); + }); + + it('with conflict blocklist and allowlist, isValidConfig should return false if blocklist check fails', function () { + expect( + helper.isValidConfig(['127.0.0.0/8'], ['https://127.0.0.1'], 'https://127.0.0.1') + ).toEqual(false); + }); +}); diff --git a/yarn.lock b/yarn.lock index 9249963758e9..d17fa56511df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8748,6 +8748,13 @@ debug@4.1.0: dependencies: ms "^2.1.1" +debug@^4: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -9218,6 +9225,14 @@ dns-packet@^1.3.1: ip "^1.1.0" safe-buffer "^5.0.1" +dns-sync@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/dns-sync/-/dns-sync-0.2.1.tgz#c519da400b90fa2e4a30a70030a1573330c72fa9" + integrity sha512-VB1pDSVs82kFsZuoHQ5/Ysx62WiIfDGn9sx/x55EoVyk8pLwdqWGB2XCaDDOusBllb+1y3XRijscFPJJfpbFiw== + dependencies: + debug "^4" + shelljs "~0.8" + dns-txt@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" @@ -13665,6 +13680,27 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== +ip-address@^6.3.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-6.4.0.tgz#8f7d43e76002a1c3c230792c748f5d8c143f908a" + integrity sha512-c5uxc2WUTuRBVHT/6r4m7HIr/DfV0bF6DvLH3iZGSK8wp8iMwwZSgIq2do0asFf8q9ECug0SE+6+1ACMe4sorA== + dependencies: + jsbn "1.1.0" + lodash.find "4.6.0" + lodash.max "4.0.1" + lodash.merge "4.6.2" + lodash.padstart "4.6.1" + lodash.repeat "4.1.0" + sprintf-js "1.1.2" + +ip-cidr@^2.1.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-2.1.4.tgz#5d687775a15afb394a1132a8f57e7f5b798c3942" + integrity sha512-dre+OGDYAYHMCm6nNoqXHwGByqMDHArVpxslJVtSxk47spvyjoiKNe7X+HV+AtBN+LxboCVuY0ARXpzibizNMg== + dependencies: + ip-address "^6.3.0" + jsbn "^1.1.0" + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -15266,6 +15302,11 @@ js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4 argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0, jsbn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA= + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -16090,6 +16131,11 @@ lodash.filter@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= +lodash.find@4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= + lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" @@ -16170,7 +16216,12 @@ lodash.map@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= -lodash.merge@^4.4.0, lodash.merge@^4.6.1: +lodash.max@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.max/-/lodash.max-4.0.1.tgz#8735566c618b35a9f760520b487ae79658af136a" + integrity sha1-hzVWbGGLNan3YFILSHrnllivE2o= + +lodash.merge@4.6.2, lodash.merge@^4.4.0, lodash.merge@^4.6.1: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -16195,7 +16246,7 @@ lodash.padend@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= -lodash.padstart@^4.1.0: +lodash.padstart@4.6.1, lodash.padstart@^4.1.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs= @@ -16220,6 +16271,11 @@ lodash.reject@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= +lodash.repeat@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.repeat/-/lodash.repeat-4.1.0.tgz#fc7de8131d8c8ac07e4b49f74ffe829d1f2bec44" + integrity sha1-/H3oEx2MisB+S0n3T/6CnR8r7EQ= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -17226,6 +17282,11 @@ ms@2.1.1, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + msgpackr-extract@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-0.3.4.tgz#8ee5e73d1135340e564c498e8c593134365eb060" @@ -21754,6 +21815,15 @@ shelljs@^0.8.3: interpret "^1.0.0" rechoir "^0.6.2" +shelljs@~0.8: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -22134,6 +22204,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@^1.0.3, sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"