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);
+ });
+ });
+});