diff --git a/server/auth/types/openid/helper.test.ts b/server/auth/types/openid/helper.test.ts index f492f1ff4..e68e8b315 100644 --- a/server/auth/types/openid/helper.test.ts +++ b/server/auth/types/openid/helper.test.ts @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -import { composeLogoutUrl } from './helper'; +import { composeLogoutUrl, getRootUrl } from './helper'; describe('test OIDC helper utility', () => { test('test compose logout url', () => { @@ -55,4 +55,86 @@ describe('test OIDC helper utility', () => { composeLogoutUrl(customLogoutUrl, idpEndSessionUrl, additionalQuery) ); }); + + test('test root url when trusted header unset', () => { + const config = { + openid: { + trust_dynamic_headers: false, + }, + }; + + const core = { + http: { + getServerInfo: () => { + return { + hostname: 'server.com', + port: 80, + protocol: 'http', + }; + }, + }, + }; + + const request = { + headers: { + 'x-forwarded-host': 'dashboards.com:443', + 'x-forwarded-proto': 'https', + }, + }; + + expect('http://server.com:80').toEqual(getRootUrl(config, core, request)); + }); + + test('test root url when trusted header set', () => { + const config = { + openid: { + trust_dynamic_headers: true, + }, + }; + + const core = { + http: { + getServerInfo: () => { + return { + hostname: 'server.com', + port: 80, + protocol: 'http', + }; + }, + }, + }; + + const request = { + headers: { + 'x-forwarded-host': 'dashboards.com:443', + 'x-forwarded-proto': 'https', + }, + }; + + expect('https://dashboards.com:443').toEqual(getRootUrl(config, core, request)); + }); + + test('test root url when trusted header set and no HTTP header', () => { + const config = { + openid: { + trust_dynamic_headers: true, + }, + }; + + const core = { + http: { + getServerInfo: () => { + return { + hostname: 'server.com', + port: 80, + protocol: 'http', + }; + }, + }, + }; + + const request = { headers: {} }; + + expect('http://server.com:80').toEqual(getRootUrl(config, core, request)); + }); }); diff --git a/server/auth/types/openid/helper.ts b/server/auth/types/openid/helper.ts index 2ba45f361..dfc948ba3 100644 --- a/server/auth/types/openid/helper.ts +++ b/server/auth/types/openid/helper.ts @@ -17,6 +17,7 @@ import wreck from '@hapi/wreck'; import { parse, stringify } from 'querystring'; import { CoreSetup } from 'opensearch-dashboards/server'; import { SecurityPluginConfigType } from '../../..'; +import { OpenSearchDashboardsRequest } from '../../../../../../src/core/server'; export function parseTokenResponse(payload: Buffer) { const payloadString = payload.toString(); @@ -30,19 +31,45 @@ export function parseTokenResponse(payload: Buffer) { return parse(payloadString); } -export function getBaseRedirectUrl(config: SecurityPluginConfigType, core: CoreSetup): string { +export function getRootUrl( + config: SecurityPluginConfigType, + core: CoreSetup, + request: OpenSearchDashboardsRequest +): string { + const host = core.http.getServerInfo().hostname; + const port = core.http.getServerInfo().port; + let protocol = core.http.getServerInfo().protocol; + let httpHost = `${host}:${port}`; + + if (config.openid?.trust_dynamic_headers) { + const xForwardedHost = (request.headers['x-forwarded-host'] as string) || undefined; + const xForwardedProto = (request.headers['x-forwarded-proto'] as string) || undefined; + if (xForwardedHost) { + httpHost = xForwardedHost; + } + if (xForwardedProto) { + protocol = xForwardedProto; + } + } + + return `${protocol}://${httpHost}`; +} + +export function getBaseRedirectUrl( + config: SecurityPluginConfigType, + core: CoreSetup, + request: OpenSearchDashboardsRequest +): string { if (config.openid?.base_redirect_url) { const baseRedirectUrl = config.openid.base_redirect_url; return baseRedirectUrl.endsWith('/') ? baseRedirectUrl.slice(0, -1) : baseRedirectUrl; } - const host = core.http.getServerInfo().hostname; - const port = core.http.getServerInfo().port; - const protocol = core.http.getServerInfo().protocol; + const rootUrl = getRootUrl(config, core, request); if (core.http.basePath.serverBasePath) { - return `${protocol}://${host}:${port}${core.http.basePath.serverBasePath}`; + return `${rootUrl}${core.http.basePath.serverBasePath}`; } - return `${protocol}://${host}:${port}`; + return rootUrl; } export async function callTokenEndpoint( diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index 078f52e6d..44e5220a6 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -89,7 +89,11 @@ export class OpenIdAuthRoutes { const query: any = { client_id: this.config.openid?.client_id, response_type: 'code', - redirect_uri: `${getBaseRedirectUrl(this.config, this.core)}/auth/openid/login`, + redirect_uri: `${getBaseRedirectUrl( + this.config, + this.core, + request + )}/auth/openid/login`, state: nonce, scope: this.openIdAuthConfig.scope, }; @@ -133,7 +137,7 @@ export class OpenIdAuthRoutes { const query: any = { grant_type: 'authorization_code', code: request.query.code, - redirect_uri: `${getBaseRedirectUrl(this.config, this.core)}/auth/openid/login`, + redirect_uri: `${getBaseRedirectUrl(this.config, this.core, request)}/auth/openid/login`, client_id: clientId, client_secret: clientSecret, }; @@ -192,7 +196,7 @@ export class OpenIdAuthRoutes { // authHeaderValue is the bearer header, e.g. "Bearer " const token = cookie?.credentials.authHeaderValue.split(' ')[1]; // get auth token const logoutQueryParams = { - post_logout_redirect_uri: getBaseRedirectUrl(this.config, this.core), + post_logout_redirect_uri: getBaseRedirectUrl(this.config, this.core, request), id_token_hint: token, }; diff --git a/server/index.ts b/server/index.ts index 287b6ebec..bf1a2699d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -135,6 +135,7 @@ export const configSchema = schema.object({ root_ca: schema.string({ defaultValue: '' }), verify_hostnames: schema.boolean({ defaultValue: true }), refresh_tokens: schema.boolean({ defaultValue: true }), + trust_dynamic_headers: schema.boolean({ defaultValue: false }), }) ), proxycache: schema.maybe(