From 87667e682afc050e83fb26f1b1ae07c3d4f0dbfe Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:27:16 -0400 Subject: [PATCH] Add Proxy Auth to Multi Auth Options (#2076) (#2093) * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options * Add Proxy Auth to Multi Auth Options --------- (cherry picked from commit c45b931b7820dad5be83964cbc857cecaf5efa6f) Signed-off-by: Stephen Crawford Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- public/apps/login/login-page.tsx | 8 +- server/auth/types/multiple/multi_auth.ts | 22 +- test/constant.ts | 4 + test/jest_integration/proxy_multiauth.test.ts | 209 ++++++++++++++++++ 4 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 test/jest_integration/proxy_multiauth.test.ts diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index d53329cd3..4591c032c 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -237,7 +237,10 @@ export function LoginPage(props: LoginPageDeps) { ); } - if (authOpts.length > 1) { + if ( + authOpts.length > 1 && + !(authOpts.includes(AuthType.PROXY) && authOpts.length === 2) + ) { formBody.push(); formBody.push(); formBody.push(); @@ -258,6 +261,9 @@ export function LoginPage(props: LoginPageDeps) { formBodyOp.push(renderLoginButton(AuthType.SAML, samlAuthLoginUrl, samlConfig)); break; } + case AuthType.PROXY: { + break; + } default: { setloginFailed(true); setloginError( diff --git a/server/auth/types/multiple/multi_auth.ts b/server/auth/types/multiple/multi_auth.ts index b00b3d154..4b4f64834 100644 --- a/server/auth/types/multiple/multi_auth.ts +++ b/server/auth/types/multiple/multi_auth.ts @@ -29,7 +29,12 @@ import { AuthType, LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { MultiAuthRoutes } from './routes'; import { SecuritySessionCookie } from '../../../session/security_cookie'; -import { BasicAuthentication, OpenIdAuthentication, SamlAuthentication } from '../../types'; +import { + BasicAuthentication, + OpenIdAuthentication, + ProxyAuthentication, + SamlAuthentication, +} from '../../types'; export class MultipleAuthentication extends AuthenticationType { private authTypes: string | string[]; @@ -93,6 +98,19 @@ export class MultipleAuthentication extends AuthenticationType { this.authHandlers.set(AuthType.SAML, SamlAuth); break; } + case AuthType.PROXY: { + const ProxyAuth = new ProxyAuthentication( + this.config, + this.sessionStorageFactory, + this.router, + this.esClient, + this.coreSetup, + this.logger + ); + await ProxyAuth.init(); + this.authHandlers.set(AuthType.PROXY, ProxyAuth); + break; + } default: { throw new Error(`Unsupported authentication type: ${this.authTypes[i]}`); } @@ -115,7 +133,7 @@ export class MultipleAuthentication extends AuthenticationType { async getAdditionalAuthHeader( request: OpenSearchDashboardsRequest ): Promise { - // To Do: refactor this method to improve the effiency to get cookie, get cookie from input parameter + // To Do: refactor this method to improve the efficiency to get cookie, get cookie from input parameter const cookie = await this.sessionStorageFactory.asScoped(request).get(); const reqAuthType = cookie?.authType?.toLowerCase(); diff --git a/test/constant.ts b/test/constant.ts index 5dcb387e2..0f450e2b8 100644 --- a/test/constant.ts +++ b/test/constant.ts @@ -21,3 +21,7 @@ export const ADMIN_PASSWORD: string = process.env.ADMIN_PASSWORD || 'admin'; const ADMIN_USER_PASS: string = `${ADMIN_USER}:${ADMIN_PASSWORD}`; export const ADMIN_CREDENTIALS: string = `Basic ${Buffer.from(ADMIN_USER_PASS).toString('base64')}`; export const AUTHORIZATION_HEADER_NAME: string = 'Authorization'; + +export const PROXY_USER: string = 'x-proxy-user'; +export const PROXY_ROLE: string = 'x-proxy-roles'; +export const PROXY_ADMIN_ROLE: string = 'admin'; diff --git a/test/jest_integration/proxy_multiauth.test.ts b/test/jest_integration/proxy_multiauth.test.ts new file mode 100644 index 000000000..125055f31 --- /dev/null +++ b/test/jest_integration/proxy_multiauth.test.ts @@ -0,0 +1,209 @@ +/* + * Copyright OpenSearch Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import * as osdTestServer from '../../../../src/core/test_helpers/osd_server'; +import { Root } from '../../../../src/core/server/root'; +import { resolve } from 'path'; +import { describe, it, beforeAll, afterAll } from '@jest/globals'; +import { + ADMIN_CREDENTIALS, + OPENSEARCH_DASHBOARDS_SERVER_USER, + OPENSEARCH_DASHBOARDS_SERVER_PASSWORD, + ADMIN_USER, + PROXY_ADMIN_ROLE, +} from '../constant'; +import wreck from '@hapi/wreck'; + +describe('start OpenSearch Dashboards server', () => { + let root: Root; + let config; + + beforeAll(async () => { + root = osdTestServer.createRootWithSettings( + { + plugins: { + scanDirs: [resolve(__dirname, '../..')], + }, + home: { disableWelcomeScreen: true }, + server: { + host: 'localhost', + port: 5601, + }, + logging: { + silent: true, + verbose: false, + }, + opensearch: { + hosts: ['https://localhost:9200'], + ignoreVersionMismatch: true, + ssl: { verificationMode: 'none' }, + username: OPENSEARCH_DASHBOARDS_SERVER_USER, + password: OPENSEARCH_DASHBOARDS_SERVER_PASSWORD, + requestHeadersAllowlist: [ + 'securitytenant', + 'Authorization', + 'x-forwarded-for', + 'x-proxy-user', + 'x-proxy-roles', + ], + }, + opensearch_security: { + auth: { + anonymous_auth_enabled: false, + type: ['basicauth', 'proxy'], + multiple_auth_enabled: true, + }, + proxycache: { + user_header: 'x-proxy-user', + roles_header: 'x-proxy-roles', + }, + multitenancy: { + enabled: true, + tenants: { + enable_global: true, + enable_private: true, + preferred: ['Private', 'Global'], + }, + }, + }, + }, + { + // to make ignoreVersionMismatch setting work + // can be removed when we have corresponding ES version + dev: true, + } + ); + + console.log('Starting OpenSearchDashboards server..'); + await root.setup(); + await root.start(); + + console.log('Starting to Download Flights Sample Data'); + await wreck.post('http://localhost:5601/api/sample_data/flights', { + payload: {}, + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + security_tenant: 'global', + }, + }); + console.log('Downloaded Sample Data'); + const getConfigResponse = await wreck.get( + 'https://localhost:9200/_plugins/_security/api/securityconfig', + { + rejectUnauthorized: false, + headers: { + authorization: ADMIN_CREDENTIALS, + }, + } + ); + const responseBody = (getConfigResponse.payload as Buffer).toString(); + config = JSON.parse(responseBody).config; + const proxyConfig = { + http_enabled: true, + transport_enabled: true, + order: 0, + http_authenticator: { + challenge: false, + type: 'proxy', + config: { + user_header: 'x-proxy-user', + roles_header: 'x-proxy-roles', + }, + }, + authentication_backend: { + type: 'noop', + config: {}, + }, + }; + try { + config.dynamic!.authc!.proxy_auth_domain = proxyConfig; + config.dynamic!.authc!.basic_internal_auth_domain.http_authenticator.challenge = false; + config.dynamic!.http!.anonymous_auth_enabled = false; + await wreck.put('https://localhost:9200/_plugins/_security/api/securityconfig/config', { + payload: config, + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }); + } catch (error) { + console.log('Got an error while updating security config!!', error.stack); + fail(error); + } + }); + + afterAll(async () => { + console.log('Remove the Sample Data'); + await wreck + .delete('http://localhost:5601/api/sample_data/flights', { + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + console.log('Remove the Security Config'); + await wreck + .patch('https://localhost:9200/_plugins/_security/api/securityconfig', { + payload: [ + { + op: 'remove', + path: '/config/dynamic/authc/proxy_auth_domain', + }, + ], + rejectUnauthorized: false, + headers: { + 'Content-Type': 'application/json', + authorization: ADMIN_CREDENTIALS, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + // shutdown OpenSearchDashboards server + await root.shutdown(); + }); + + it('Verify Proxy access to dashboards', async () => { + console.log('Wreck access home page'); + await wreck + .get('http://localhost:5601/app/home#', { + rejectUnauthorized: true, + headers: { + 'Content-Type': 'application/json', + PROXY_USER: ADMIN_USER, + PROXY_ROLE: PROXY_ADMIN_ROLE, + }, + }) + .then((value) => { + Promise.resolve(value); + }) + .catch((value) => { + Promise.resolve(value); + }); + }); +});