Skip to content

Commit

Permalink
[config] Add externalRequest options to separate requests; for bug 63590
Browse files Browse the repository at this point in the history
  • Loading branch information
konovalovsergey committed Feb 18, 2024
1 parent 8318118 commit 42a3822
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 33 deletions.
17 changes: 17 additions & 0 deletions Common/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@
"useClones": false
}
},
"externalRequest": {
"directIfIn" : {
"allowList": [],
"jwtToken": true
},
"action": {
"allow": true,
"blockPrivateIP": true,
"proxyUrl": "",
"proxyAuth": {
"username": "",
"password": ""
},
"proxyHeaders": {
}
}
},
"services": {
"CoAuthoring": {
"server": {
Expand Down
4 changes: 2 additions & 2 deletions Common/config/development-windows.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"dbPass": "onlyoffice"
},
"request-filtering-agent" : {
"allowPrivateIPAddress": true,
"allowMetaIPAddress": true
"allowPrivateIPAddress": false,
"allowMetaIPAddress": false
},
"sockjs": {
"sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js"
Expand Down
62 changes: 57 additions & 5 deletions Common/sources/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ const cfgPasswordEncrypt = config.get('openpgpjs.encrypt');
const cfgPasswordDecrypt = config.get('openpgpjs.decrypt');
const cfgPasswordConfig = config.get('openpgpjs.config');
const cfgRequesFilteringAgent = config.get('services.CoAuthoring.request-filtering-agent');
const cfgAllowPrivateIPAddressForSignedRequests = config.get('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests');
const cfgStorageExternalHost = config.get('storage.externalHost');
const cfgExternalRequestDirectIfIn = config.get('externalRequest.directIfIn');
const cfgExternalRequestAction = config.get('externalRequest.action');

const dnscache = getDnsCache(cfgDnsCache);

Expand Down Expand Up @@ -266,6 +269,54 @@ function raiseErrorObj(ro, error) {
function isRedirectResponse(response) {
return response && response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location');
}

function isAllowDirectRequest(ctx, uri, isInJwtToken) {
let res = false;
const tenExternalRequestDirectIfIn = ctx.getCfg('externalRequest.directIfIn', cfgExternalRequestDirectIfIn);
const tenAllowPrivateIPAddressForSignedRequests = ctx.getCfg('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests', cfgAllowPrivateIPAddressForSignedRequests);
let allowList = tenExternalRequestDirectIfIn.allowList;
if (allowList.length > 0) {
let allowIndex = allowList.findIndex((allowPrefix) => {
return uri.startsWith(allowPrefix);
}, uri);
res = -1 !== allowIndex;
ctx.logger.debug("isAllowDirectRequest check allow list res=%s", res);
} else if (tenExternalRequestDirectIfIn.jwtToken && tenAllowPrivateIPAddressForSignedRequests) {
res = isInJwtToken;
ctx.logger.debug("isAllowDirectRequest url in jwt token res=%s", res);
}
return res;
}
function addExternalRequestOptions(ctx, uri, isInJwtToken, options) {
let res = false;
const tenExternalRequestAction = ctx.getCfg('externalRequest.action', cfgExternalRequestAction);
const tenRequesFilteringAgent = ctx.getCfg('services.CoAuthoring.request-filtering-agent', cfgRequesFilteringAgent);
if (isAllowDirectRequest(ctx, uri, isInJwtToken)) {
res = true;
} else if (tenExternalRequestAction.allow) {
res = true;
if (tenExternalRequestAction.blockPrivateIP) {
const agentOptions = Object.assign({}, https.globalAgent.options, tenRequesFilteringAgent);
options.agent = getRequestFilterAgent(uri, agentOptions);
}
if (tenExternalRequestAction.proxyUrl) {
options.proxy = tenExternalRequestAction.proxyUrl;
}
if (tenExternalRequestAction.proxyUser?.username) {
let user = tenExternalRequestAction.proxyUser.username;
let pass = tenExternalRequestAction.proxyUser.password;
options.headers = {'proxy-authorization': `${user}:${pass}`};
}
if (tenExternalRequestAction.proxyHeaders) {
if (!options.headers) {
options.headers = {};
}
Object.assign(options.headers, tenExternalRequestAction.proxyHeaders);
}
}
return res;
}

function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) {
//todo replace deprecated request module
const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults);
Expand Down Expand Up @@ -298,7 +349,6 @@ function downloadUrlPromiseWithoutRedirect(ctx, uri, optTimeout, optLimit, opt_A
const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults);
const tenTokenOutboxHeader = ctx.getCfg('services.CoAuthoring.token.outbox.header', cfgTokenOutboxHeader);
const tenTokenOutboxPrefix = ctx.getCfg('services.CoAuthoring.token.outbox.prefix', cfgTokenOutboxPrefix);
const tenRequesFilteringAgent = ctx.getCfg('services.CoAuthoring.request-filtering-agent', cfgRequesFilteringAgent);
//IRI to URI
uri = URI.serialize(URI.parse(uri));
var urlParsed = url.parse(uri);
Expand All @@ -309,10 +359,12 @@ function downloadUrlPromiseWithoutRedirect(ctx, uri, optTimeout, optLimit, opt_A
let connectionAndInactivity = optTimeout && optTimeout.connectionAndInactivity && ms(optTimeout.connectionAndInactivity);
let options = config.util.extendDeep({}, tenTenantRequestDefaults);
Object.assign(options, {uri: urlParsed, encoding: null, timeout: connectionAndInactivity, followRedirect: false});
if (opt_filterPrivate) {
const agentOptions = Object.assign({}, https.globalAgent.options, tenRequesFilteringAgent);
options.agent = getRequestFilterAgent(uri, agentOptions);
} else {
if (!addExternalRequestOptions(ctx, uri, opt_filterPrivate, options)) {
reject(new Error('Block external request. See externalRequest config options'));
return;
}

if (!options.agent) {
//baseRequest creates new agent(win-ca injects in globalAgent)
options.agentOptions = https.globalAgent.options;
}
Expand Down
16 changes: 9 additions & 7 deletions DocService/sources/canvasservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ const cfgAssemblyFormatAsOrigin = config.get('services.CoAuthoring.server.assemb
const cfgDownloadMaxBytes = config.get('FileConverter.converter.maxDownloadBytes');
const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout');
const cfgDownloadFileAllowExt = config.get('services.CoAuthoring.server.downloadFileAllowExt');
const cfgAllowPrivateIPAddressForSignedRequests = config.get('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests');

var SAVE_TYPE_PART_START = 0;
var SAVE_TYPE_PART = 1;
Expand Down Expand Up @@ -658,11 +657,11 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize);
const tenImageDownloadTimeout = ctx.getCfg('services.CoAuthoring.server.limits_image_download_timeout', cfgImageDownloadTimeout);
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
const tenAllowPrivateIPAddressForSignedRequests = ctx.getCfg('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests', cfgAllowPrivateIPAddressForSignedRequests);

var errorCode = constants.NO_ERROR;
let urls = cmd.getData();
let authorizations = [];
let isInJwtToken = false;
let token = cmd.getTokenDownload();
if (tenTokenEnableBrowser && token) {
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
Expand All @@ -681,6 +680,7 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
authorizations[i] = [utils.fillJwtForRequest(ctx, {url: urls[i]}, secret, false)];
}
}
isInJwtToken = true;
} else {
ctx.logger.warn('Error commandImgurls jwt: %s', checkJwtRes.description);
errorCode = constants.VKEY_ENCRYPT;
Expand Down Expand Up @@ -723,8 +723,7 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
}
}
//todo stream
const filterPrivate = !authorizations[i] || !tenAllowPrivateIPAddressForSignedRequests;
let getRes = yield utils.downloadUrlPromise(ctx, urlSource, tenImageDownloadTimeout, tenImageSize, authorizations[i], filterPrivate);
let getRes = yield utils.downloadUrlPromise(ctx, urlSource, tenImageDownloadTimeout, tenImageSize, authorizations[i], isInJwtToken);
data = getRes.body;
urlParsed = urlModule.parse(urlSource);
} catch (e) {
Expand Down Expand Up @@ -1595,23 +1594,27 @@ exports.downloadFile = function(req, res) {
const tenDownloadMaxBytes = ctx.getCfg('FileConverter.converter.maxDownloadBytes', cfgDownloadMaxBytes);
const tenDownloadTimeout = ctx.getCfg('FileConverter.converter.downloadTimeout', cfgDownloadTimeout);
const tenDownloadFileAllowExt = ctx.getCfg('services.CoAuthoring.server.downloadFileAllowExt', cfgDownloadFileAllowExt);
const tenAllowPrivateIPAddressForSignedRequests = ctx.getCfg('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests', cfgAllowPrivateIPAddressForSignedRequests);

let authorization;
let isInJwtToken = false;
let errorDescription;
let authRes = yield docsCoServer.getRequestParams(ctx, req);
if (authRes.code === constants.NO_ERROR) {
let decoded = authRes.params;
if (decoded.changesUrl) {
url = decoded.changesUrl;
isInJwtToken = true;
} else if (decoded.document && -1 !== tenDownloadFileAllowExt.indexOf(decoded.document.fileType)) {
url = decoded.document.url;
isInJwtToken = true;
} else if (decoded.url && -1 !== tenDownloadFileAllowExt.indexOf(decoded.fileType)) {
url = decoded.url;
isInJwtToken = true;
} else if (!tenTokenEnableBrowser) {
//todo token required
if (decoded.url) {
url = decoded.url;
isInJwtToken = true;
}
} else {
errorDescription = 'access deny';
Expand Down Expand Up @@ -1642,8 +1645,7 @@ exports.downloadFile = function(req, res) {
}
}

const filterPrivate = !authorization || !tenAllowPrivateIPAddressForSignedRequests;
yield utils.downloadUrlPromise(ctx, url, tenDownloadTimeout, tenDownloadMaxBytes, authorization, filterPrivate, headers, res);
yield utils.downloadUrlPromise(ctx, url, tenDownloadTimeout, tenDownloadMaxBytes, authorization, isInJwtToken, headers, res);

if (clientStatsD) {
clientStatsD.timing('coauth.downloadFile', new Date() - startDate);
Expand Down
7 changes: 3 additions & 4 deletions DocService/sources/wopiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.al
const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires');
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callbackRequestTimeout');
const cfgAllowPrivateIPAddressForSignedRequests = config.get('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests');
const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate');
const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout');
const cfgWopiFileInfoBlockList = config.get('wopi.fileInfoBlockList');
Expand Down Expand Up @@ -701,7 +700,6 @@ function checkFileInfo(ctx, wopiSrc, access_token, opt_sc) {
let fileInfo = undefined;
try {
ctx.logger.info('wopi checkFileInfo start');
const tenAllowPrivateIPAddressForSignedRequests = ctx.getCfg('services.CoAuthoring.server.allowPrivateIPAddressForSignedRequests', cfgAllowPrivateIPAddressForSignedRequests);
const tenDownloadTimeout = ctx.getCfg('FileConverter.converter.downloadTimeout', cfgDownloadTimeout);

let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`;
Expand All @@ -715,8 +713,9 @@ function checkFileInfo(ctx, wopiSrc, access_token, opt_sc) {
}
fillStandardHeaders(ctx, headers, uri, access_token);
ctx.logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers);
const filterPrivate = !tenAllowPrivateIPAddressForSignedRequests;
let getRes = yield utils.downloadUrlPromise(ctx, uri, tenDownloadTimeout, undefined, undefined, filterPrivate, headers);
//todo false?
let isInJwtToken = true;
let getRes = yield utils.downloadUrlPromise(ctx, uri, tenDownloadTimeout, undefined, undefined, isInJwtToken, headers);
ctx.logger.debug(`wopi checkFileInfo headers=%j body=%s`, getRes.response.headers, getRes.body);
fileInfo = JSON.parse(getRes.body);
} catch (err) {
Expand Down
Loading

0 comments on commit 42a3822

Please sign in to comment.