From 3025f079b898665b928f84a0a50c79127e82148e Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Mon, 6 Jan 2025 14:12:10 +0100 Subject: [PATCH 01/13] OIDC issuer behind a proxy cannot be accessed Fixes #942 --- app/server/lib/OIDCConfig.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/server/lib/OIDCConfig.ts b/app/server/lib/OIDCConfig.ts index b14a269350..74fcf1761c 100644 --- a/app/server/lib/OIDCConfig.ts +++ b/app/server/lib/OIDCConfig.ts @@ -80,6 +80,7 @@ import { StringUnionError } from 'app/common/StringUnion'; import { EnabledProtection, EnabledProtectionString, ProtectionsManager } from './oidc/Protections'; import { SessionObj } from './BrowserSession'; import { getOriginUrl } from './requestUtils'; +import { proxyAgent } from './ProxyAgent'; const CALLBACK_URL = '/oauth2/callback'; @@ -181,6 +182,7 @@ export class OIDCConfig { this._redirectUrl = new URL(CALLBACK_URL, spHost).href; custom.setHttpOptionsDefaults({ + agent: issuerUrl !== undefined ? proxyAgent(new URL(issuerUrl)) : undefined, ...(httpTimeout !== undefined ? {timeout: httpTimeout} : {}), }); await this._initClient({ issuerUrl, clientId, clientSecret, extraMetadata }); From 337859a2e1f038f04e8ef1d271965ca083f5979e Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Mon, 6 Jan 2025 14:55:53 +0100 Subject: [PATCH 02/13] fixes oidc config test --- app/server/lib/OIDCConfig.ts | 2 +- test/server/lib/OIDCConfig.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/server/lib/OIDCConfig.ts b/app/server/lib/OIDCConfig.ts index 74fcf1761c..68132962a7 100644 --- a/app/server/lib/OIDCConfig.ts +++ b/app/server/lib/OIDCConfig.ts @@ -182,7 +182,7 @@ export class OIDCConfig { this._redirectUrl = new URL(CALLBACK_URL, spHost).href; custom.setHttpOptionsDefaults({ - agent: issuerUrl !== undefined ? proxyAgent(new URL(issuerUrl)) : undefined, + ...(issuerUrl !== undefined ? {agent: proxyAgent(new URL(issuerUrl))} : {}), ...(httpTimeout !== undefined ? {timeout: httpTimeout} : {}), }); await this._initClient({ issuerUrl, clientId, clientSecret, extraMetadata }); diff --git a/test/server/lib/OIDCConfig.ts b/test/server/lib/OIDCConfig.ts index d7f46814d2..f3422b1609 100644 --- a/test/server/lib/OIDCConfig.ts +++ b/test/server/lib/OIDCConfig.ts @@ -197,7 +197,7 @@ describe('OIDCConfig', () => { [ { itMsg: 'when omitted should not override openid-client default value', - expectedUserDefinedHttpOptions: {} + expectedUserDefinedHttpOptions: { agent: undefined } }, { itMsg: 'should reject when the provided value is not a number', @@ -213,6 +213,7 @@ describe('OIDCConfig', () => { }, shouldSetTimeout: true, expectedUserDefinedHttpOptions: { + agent: undefined, timeout: 10000 } }, @@ -222,6 +223,7 @@ describe('OIDCConfig', () => { GRIST_OIDC_SP_HTTP_TIMEOUT: '0', }, expectedUserDefinedHttpOptions: { + agent: undefined, timeout: 0 } } From e15d5e59f37e2a044b67cd74f39e016d35863310 Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Wed, 8 Jan 2025 14:02:13 +0100 Subject: [PATCH 03/13] OIDC issuer behind a proxy cannot be accessed Fixes #942 --- app/server/lib/OIDCConfig.ts | 3 ++- test/server/lib/OIDCConfig.ts | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/server/lib/OIDCConfig.ts b/app/server/lib/OIDCConfig.ts index 68132962a7..6cc65cf77e 100644 --- a/app/server/lib/OIDCConfig.ts +++ b/app/server/lib/OIDCConfig.ts @@ -181,8 +181,9 @@ export class OIDCConfig { this._protectionManager = new ProtectionsManager(enabledProtections); this._redirectUrl = new URL(CALLBACK_URL, spHost).href; + const agent = issuerUrl !== undefined ? proxyAgent(new URL(issuerUrl)) : undefined; custom.setHttpOptionsDefaults({ - ...(issuerUrl !== undefined ? {agent: proxyAgent(new URL(issuerUrl))} : {}), + ...(agent !== undefined ? {agent} : {}), ...(httpTimeout !== undefined ? {timeout: httpTimeout} : {}), }); await this._initClient({ issuerUrl, clientId, clientSecret, extraMetadata }); diff --git a/test/server/lib/OIDCConfig.ts b/test/server/lib/OIDCConfig.ts index f3422b1609..8e7559878b 100644 --- a/test/server/lib/OIDCConfig.ts +++ b/test/server/lib/OIDCConfig.ts @@ -10,6 +10,7 @@ import express from "express"; import _ from "lodash"; import {RequestWithLogin} from "app/server/lib/Authorizer"; import { SendAppPageFunction } from "app/server/lib/sendAppPage"; +import {HttpProxyAgent} from "http-proxy-agent"; const NOOPED_SEND_APP_PAGE: SendAppPageFunction = () => Promise.resolve(); @@ -197,7 +198,7 @@ describe('OIDCConfig', () => { [ { itMsg: 'when omitted should not override openid-client default value', - expectedUserDefinedHttpOptions: { agent: undefined } + expectedUserDefinedHttpOptions: { } }, { itMsg: 'should reject when the provided value is not a number', @@ -213,7 +214,6 @@ describe('OIDCConfig', () => { }, shouldSetTimeout: true, expectedUserDefinedHttpOptions: { - agent: undefined, timeout: 10000 } }, @@ -223,7 +223,6 @@ describe('OIDCConfig', () => { GRIST_OIDC_SP_HTTP_TIMEOUT: '0', }, expectedUserDefinedHttpOptions: { - agent: undefined, timeout: 0 } } @@ -243,6 +242,36 @@ describe('OIDCConfig', () => { }); }); }); + + describe('GRIST_HTTPS_PROXY', function () { + const proxyURL = 'http://localhost-proxy8080'; + const httpAgent = new HttpProxyAgent(proxyURL); + [ + { + itMsg: 'when omitted should not set proxyAgent to oidc-client', + expectedUserDefinedHttpOptions: { } + }, + { + itMsg: 'should add proxyAgent to openid-client', + env: { + GRIST_HTTPS_PROXY: proxyURL, + }, + expectedUserDefinedHttpOptions: { + agent: httpAgent + } + } + ].forEach(ctx => { + it(ctx.itMsg, async () => { + const setHttpOptionsDefaultsStub = sandbox.stub(custom, 'setHttpOptionsDefaults'); + setEnvVars(); + Object.assign(process.env, ctx.env); + const promise = OIDCConfigStubbed.buildWithStub(); + await assert.isFulfilled(promise, 'initOIDC should have been fulfilled'); + assert.isTrue(setHttpOptionsDefaultsStub.calledOnce, 'Should have called custom.setHttpOptionsDefaults'); + Sinon.assert.match(setHttpOptionsDefaultsStub.firstCall.args[0], ctx.expectedUserDefinedHttpOptions); + }); + }); + }); }); describe('GRIST_OIDC_IDP_ENABLED_PROTECTIONS', () => { From 0584b854787b9515bec33063dbecb818c917f838 Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Fri, 10 Jan 2025 08:31:24 +0100 Subject: [PATCH 04/13] Update app/server/lib/OIDCConfig.ts Co-authored-by: Florent --- app/server/lib/OIDCConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/lib/OIDCConfig.ts b/app/server/lib/OIDCConfig.ts index 6cc65cf77e..44c562bdf1 100644 --- a/app/server/lib/OIDCConfig.ts +++ b/app/server/lib/OIDCConfig.ts @@ -181,7 +181,7 @@ export class OIDCConfig { this._protectionManager = new ProtectionsManager(enabledProtections); this._redirectUrl = new URL(CALLBACK_URL, spHost).href; - const agent = issuerUrl !== undefined ? proxyAgent(new URL(issuerUrl)) : undefined; + const agent = proxyAgent(new URL(issuerUrl)); custom.setHttpOptionsDefaults({ ...(agent !== undefined ? {agent} : {}), ...(httpTimeout !== undefined ? {timeout: httpTimeout} : {}), From b4e0a4eb8e5ca41ed5ba7acd5d77a98030ad7a4c Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Fri, 10 Jan 2025 08:32:32 +0100 Subject: [PATCH 05/13] Update test/server/lib/OIDCConfig.ts Co-authored-by: Florent --- test/server/lib/OIDCConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/server/lib/OIDCConfig.ts b/test/server/lib/OIDCConfig.ts index 8e7559878b..8880e46c31 100644 --- a/test/server/lib/OIDCConfig.ts +++ b/test/server/lib/OIDCConfig.ts @@ -244,7 +244,7 @@ describe('OIDCConfig', () => { }); describe('GRIST_HTTPS_PROXY', function () { - const proxyURL = 'http://localhost-proxy8080'; + const proxyURL = 'http://localhost-proxy:8080'; const httpAgent = new HttpProxyAgent(proxyURL); [ { From 0b45e5c1c96892b4e18ee66838ce376fed83c444 Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Fri, 10 Jan 2025 09:38:00 +0100 Subject: [PATCH 06/13] Update test/server/lib/OIDCConfig.ts Co-authored-by: Florent --- test/server/lib/OIDCConfig.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/server/lib/OIDCConfig.ts b/test/server/lib/OIDCConfig.ts index 8880e46c31..b4cb08cd18 100644 --- a/test/server/lib/OIDCConfig.ts +++ b/test/server/lib/OIDCConfig.ts @@ -268,7 +268,8 @@ describe('OIDCConfig', () => { const promise = OIDCConfigStubbed.buildWithStub(); await assert.isFulfilled(promise, 'initOIDC should have been fulfilled'); assert.isTrue(setHttpOptionsDefaultsStub.calledOnce, 'Should have called custom.setHttpOptionsDefaults'); - Sinon.assert.match(setHttpOptionsDefaultsStub.firstCall.args[0], ctx.expectedUserDefinedHttpOptions); + const actualHttpOptions = _.omit(setHttpOptionsDefaultsStub.firstCall.args[0], 'agent.callback'); + assert.deepEqual(actualHttpOptions, ctx.expectedUserDefinedHttpOptions); }); }); }); From 19f556de17beb96f9f8d7a4628151fb53aa425ed Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Fri, 10 Jan 2025 11:35:05 +0100 Subject: [PATCH 07/13] OIDC issuer behind a proxy cannot be accessed Fixes #942 --- test/server/lib/OIDCConfig.ts | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/server/lib/OIDCConfig.ts b/test/server/lib/OIDCConfig.ts index b4cb08cd18..2fde13e284 100644 --- a/test/server/lib/OIDCConfig.ts +++ b/test/server/lib/OIDCConfig.ts @@ -243,36 +243,36 @@ describe('OIDCConfig', () => { }); }); - describe('GRIST_HTTPS_PROXY', function () { - const proxyURL = 'http://localhost-proxy:8080'; - const httpAgent = new HttpProxyAgent(proxyURL); - [ - { - itMsg: 'when omitted should not set proxyAgent to oidc-client', - expectedUserDefinedHttpOptions: { } - }, - { - itMsg: 'should add proxyAgent to openid-client', - env: { - GRIST_HTTPS_PROXY: proxyURL, + describe('GRIST_HTTPS_PROXY', function () { + const proxyURL = 'http://localhost-proxy:8080'; + const httpAgent = new HttpProxyAgent(proxyURL); + [ + { + itMsg: 'when omitted should not set proxyAgent to oidc-client', + expectedUserDefinedHttpOptions: { } }, - expectedUserDefinedHttpOptions: { - agent: httpAgent + { + itMsg: 'should add proxyAgent to openid-client', + env: { + GRIST_HTTPS_PROXY: proxyURL, + }, + expectedUserDefinedHttpOptions: { + agent: httpAgent + } } - } - ].forEach(ctx => { - it(ctx.itMsg, async () => { - const setHttpOptionsDefaultsStub = sandbox.stub(custom, 'setHttpOptionsDefaults'); - setEnvVars(); - Object.assign(process.env, ctx.env); - const promise = OIDCConfigStubbed.buildWithStub(); - await assert.isFulfilled(promise, 'initOIDC should have been fulfilled'); - assert.isTrue(setHttpOptionsDefaultsStub.calledOnce, 'Should have called custom.setHttpOptionsDefaults'); - const actualHttpOptions = _.omit(setHttpOptionsDefaultsStub.firstCall.args[0], 'agent.callback'); - assert.deepEqual(actualHttpOptions, ctx.expectedUserDefinedHttpOptions); + ].forEach(ctx => { + it(ctx.itMsg, async () => { + const setHttpOptionsDefaultsStub = sandbox.stub(custom, 'setHttpOptionsDefaults'); + setEnvVars(); + Object.assign(process.env, ctx.env); + const promise = OIDCConfigStubbed.buildWithStub(); + await assert.isFulfilled(promise, 'initOIDC should have been fulfilled'); + assert.isTrue(setHttpOptionsDefaultsStub.calledOnce, 'Should have called custom.setHttpOptionsDefaults'); + const actualHttpOptions = _.omit(setHttpOptionsDefaultsStub.firstCall.args[0], 'agent.callback'); + assert.deepEqual(actualHttpOptions, ctx.expectedUserDefinedHttpOptions); + }); }); - }); - }); + }); }); describe('GRIST_OIDC_IDP_ENABLED_PROTECTIONS', () => { From cf4a060991ee3d3be746eb2a2c1cb2e2ccbdefc6 Mon Sep 17 00:00:00 2001 From: Tristan Robert Date: Mon, 20 Jan 2025 13:37:45 +0100 Subject: [PATCH 08/13] refactor: :recycle: refactors proxyAgent in two trusted and unstrusted agents As discussed in https://github.com/gristlabs/grist-core/pull/1363#issuecomment-2596076325 --- README.md | 3 +- app/server/lib/OIDCConfig.ts | 4 +- app/server/lib/ProxyAgent.ts | 33 +++++++++-- app/server/lib/Requests.ts | 4 +- app/server/lib/Triggers.ts | 4 +- app/server/lib/WidgetRepository.ts | 6 +- test/server/lib/OIDCConfig.ts | 4 +- test/server/lib/ProxyAgent.ts | 92 +++++++++++++++++++++++++----- 8 files changed, 121 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 607e934e3e..4461290b9c 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,8 @@ Grist can be configured in many ways. Here are the main environment variables it | GRIST_ENABLE_REQUEST_FUNCTION | enables the REQUEST function. This function performs HTTP requests in a similar way to `requests.request`. This function presents a significant security risk, since it can let users call internal endpoints when Grist is available publicly. This function can also cause performance issues. Unset by default. | | GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled. | | GRIST_HOST | hostname to use when listening on a port. | -| GRIST_HTTPS_PROXY | if set, use this proxy for webhook payload delivery or fetching custom widgets repository from url. | +| GRIST_HTTPS_PROXY | if set with url, use this proxy for webhook payload delivery; if not set or set to direct it call direct webhook payload. | +| HTTPS_PROXY or https_proxy | if set, use this reverse web proxy (corporate proxy) for fetching custom widgets repository from url or OIDC config from issuer url outside the grist stack. | | GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*. | | GRIST_IGNORE_SESSION | if set, Grist will not use a session for authentication. | | GRIST_INCLUDE_CUSTOM_SCRIPT_URL | if set, will load the referenced URL in a `