From ca8b4ef9133ceabaa13c557f340ceb6a0dbcfc44 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 20 Mar 2024 17:50:57 -0400 Subject: [PATCH] Adds saml auth header to differentiate saml requests Signed-off-by: Darshit Chanpura --- common/index.ts | 7 +++---- server/auth/types/authentication_type.ts | 11 +---------- server/auth/types/basic/basic_auth.ts | 4 ++-- server/auth/types/basic/routes.ts | 5 +---- server/auth/types/openid/routes.ts | 3 ++- server/auth/types/saml/routes.ts | 15 ++++++++++----- server/backend/opensearch_security_client.ts | 15 +++++++++++---- server/readonly/readonly_service.ts | 1 - test/jest_integration/basic_auth.test.ts | 7 ++----- 9 files changed, 32 insertions(+), 36 deletions(-) diff --git a/common/index.ts b/common/index.ts index a758ca00e..482ce6ecb 100644 --- a/common/index.ts +++ b/common/index.ts @@ -36,10 +36,9 @@ export const OPENID_AUTH_LOGIN_WITH_FRAGMENT = '/auth/openid/captureUrlFragment' export const SAML_AUTH_LOGIN = '/auth/saml/login'; export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment'; export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous'; -const ANONYMOUS_AUTH_USER: string = 'opendistro_security_anonymous'; -const RANDOM_PASS: string = randomString(12); -const ANONYMOUS_USER_PASS: string = `${ANONYMOUS_AUTH_USER}:${RANDOM_PASS}`; -export const ANONYMOUS_AUTH_HEADER = `Basic ${Buffer.from(ANONYMOUS_USER_PASS).toString('base64')}`; +export const AUTH_REQUEST_TYPE_HEADER = 'auth_request_type'; +export const SAML_AUTH_REQUEST_TYPE = 'saml'; + export const OPENID_AUTH_LOGOUT = '/auth/openid/logout'; export const SAML_AUTH_LOGOUT = '/auth/saml/logout'; diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts index e58b2a5ce..b516e3942 100755 --- a/server/auth/types/authentication_type.ts +++ b/server/auth/types/authentication_type.ts @@ -31,7 +31,7 @@ import { SecuritySessionCookie } from '../../session/security_cookie'; import { SecurityClient } from '../../backend/opensearch_security_client'; import { resolveTenant, isValidTenant } from '../../multitenancy/tenant_resolver'; import { UnauthenticatedError } from '../../errors'; -import { ANONYMOUS_AUTH_HEADER, GLOBAL_TENANT_SYMBOL } from '../../../common'; +import { GLOBAL_TENANT_SYMBOL } from '../../../common'; export interface IAuthenticationType { type: string; @@ -114,15 +114,6 @@ export abstract class AuthenticationType implements IAuthenticationType { let cookie: SecuritySessionCookie | null | undefined; let authInfo: any | undefined; - // Adds a basic auth credentials headers to requests originated as anonymous user - if ( - this.config.auth.anonymous_auth_enabled && - !request.headers.hasOwnProperty('authorization') - ) { - const anonymousAuthHeaders = { authorization: ANONYMOUS_AUTH_HEADER }; - Object.assign(authHeaders, anonymousAuthHeaders); - } - // if this is an REST API call, suppose the request includes necessary auth header // see https://www.elastic.co/guide/en/opensearch-dashboards/master/using-api.html if (this.requestIncludesAuthInfo(request)) { diff --git a/server/auth/types/basic/basic_auth.ts b/server/auth/types/basic/basic_auth.ts index 539257bf3..4bea50c15 100644 --- a/server/auth/types/basic/basic_auth.ts +++ b/server/auth/types/basic/basic_auth.ts @@ -28,7 +28,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecuritySessionCookie } from '../../../session/security_cookie'; import { BasicAuthRoutes } from './routes'; import { AuthenticationType } from '../authentication_type'; -import { LOGIN_PAGE_URI, ANONYMOUS_AUTH_HEADER } from '../../../../common'; +import { LOGIN_PAGE_URI } from '../../../../common'; import { composeNextUrlQueryParam } from '../../../utils/next_url'; import { AUTH_HEADER_NAME, AuthType, OPENDISTRO_SECURITY_ANONYMOUS } from '../../../../common'; @@ -130,7 +130,7 @@ export class BasicAuthentication extends AuthenticationType { request: OpenSearchDashboardsRequest ): any { if (this.config.auth.anonymous_auth_enabled && cookie.isAnonymousAuth) { - return { authorization: ANONYMOUS_AUTH_HEADER }; + return { }; } const headers: any = {}; Object.assign(headers, { authorization: cookie.credentials?.authHeaderValue }); diff --git a/server/auth/types/basic/routes.ts b/server/auth/types/basic/routes.ts index 4370c63a3..0319ac2ea 100755 --- a/server/auth/types/basic/routes.ts +++ b/server/auth/types/basic/routes.ts @@ -23,7 +23,6 @@ import { SecurityPluginConfigType } from '../../..'; import { User } from '../../user'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { - ANONYMOUS_AUTH_HEADER, ANONYMOUS_AUTH_LOGIN, API_AUTH_LOGIN, API_AUTH_LOGOUT, @@ -187,9 +186,7 @@ export class BasicAuthRoutes { } context.security_plugin.logger.info('The Redirect Path is ' + redirectUrl); try { - user = await this.securityClient.authenticateWithHeaders(request, { - authorization: ANONYMOUS_AUTH_HEADER, - }); + user = await this.securityClient.authenticateWithHeaders(request, { }); } catch (error) { context.security_plugin.logger.error( `Failed authentication: ${error}. Redirecting to Login Page` diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index a8dc2e2b1..7309690de 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -188,7 +188,8 @@ export class OpenIdAuthRoutes { const user = await this.securityClient.authenticateWithHeader( request, this.openIdAuthConfig.authHeaderName as string, - `Bearer ${tokenResponse.idToken}` + `Bearer ${tokenResponse.idToken}`, + undefined ); // set to cookie diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 87605d65e..dc0208057 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -20,7 +20,7 @@ import { SecurityPluginConfigType } from '../../..'; import { SecurityClient } from '../../../backend/opensearch_security_client'; import { CoreSetup } from '../../../../../../src/core/server'; import { validateNextUrl } from '../../../utils/next_url'; -import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT } from '../../../../common'; +import { AuthType, SAML_AUTH_LOGIN, SAML_AUTH_LOGOUT, SAML_AUTH_REQUEST_TYPE } from '../../../../common'; import { clearSplitCookies, @@ -120,6 +120,7 @@ export class SamlAuthRoutes { `${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`; redirectHash = cookie.saml?.redirectHash || false; } + console.log(requestId); if (!requestId) { return response.badRequest({ body: 'Invalid requestId', @@ -134,12 +135,14 @@ export class SamlAuthRoutes { const credentials = await this.securityClient.authToken( requestId, request.body.SAMLResponse, - undefined + undefined, + SAML_AUTH_REQUEST_TYPE ); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization + credentials.authorization, + SAML_AUTH_REQUEST_TYPE ); let expiryTime = Date.now() + this.config.session.ttl; @@ -211,12 +214,14 @@ export class SamlAuthRoutes { const credentials = await this.securityClient.authToken( undefined, request.body.SAMLResponse, - acsEndpoint + acsEndpoint, + SAML_AUTH_REQUEST_TYPE ); const user = await this.securityClient.authenticateWithHeader( request, 'authorization', - credentials.authorization + credentials.authorization, + SAML_AUTH_REQUEST_TYPE ); let expiryTime = Date.now() + this.config.session.ttl; diff --git a/server/backend/opensearch_security_client.ts b/server/backend/opensearch_security_client.ts index 7897444e4..70965acc2 100755 --- a/server/backend/opensearch_security_client.ts +++ b/server/backend/opensearch_security_client.ts @@ -15,8 +15,9 @@ import { ILegacyClusterClient, OpenSearchDashboardsRequest } from '../../../../src/core/server'; import { User } from '../auth/user'; -import { getAuthInfo } from '../../public/utils/auth-info-utils'; import { TenancyConfigSettings } from '../../public/apps/configuration/panels/tenancy-config/types'; +import { AUTH_REQUEST_TYPE_HEADER, SAML_AUTH_REQUEST_TYPE } from '../../common'; + export class SecurityClient { constructor(private readonly esClient: ILegacyClusterClient) {} @@ -52,8 +53,9 @@ export class SecurityClient { request: OpenSearchDashboardsRequest, headerName: string, headerValue: string, + authRequestType: string | undefined, whitelistedHeadersAndValues: any = {}, - additionalAuthHeaders: any = {} + additionalAuthHeaders: any = {}, ): Promise { try { const credentials: any = { @@ -64,6 +66,7 @@ export class SecurityClient { if (headerValue) { headers[headerName] = headerValue; } + headers.AUTH_REQUEST_TYPE_HEADER = authRequestType; // cannot get config elasticsearch.requestHeadersWhitelist from kibana.yml file in new platfrom // meanwhile, do we really need to save all headers in cookie? @@ -183,7 +186,9 @@ export class SecurityClient { public async getSamlHeader(request: OpenSearchDashboardsRequest) { try { // response is expected to be an error - await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo'); + await this.esClient.asScoped(request).callAsCurrentUser('opensearch_security.authinfo', { + [AUTH_REQUEST_TYPE_HEADER]: SAML_AUTH_REQUEST_TYPE, + }); } catch (error: any) { // the error looks like // wwwAuthenticateDirective: @@ -221,7 +226,8 @@ export class SecurityClient { public async authToken( requestId: string | undefined, samlResponse: any, - acsEndpoint: any | undefined = undefined + acsEndpoint: any | undefined = undefined, + authRequestType: string | undefined ) { const body = { RequestId: requestId, @@ -231,6 +237,7 @@ export class SecurityClient { try { return await this.esClient.asScoped().callAsCurrentUser('opensearch_security.authtoken', { body, + [AUTH_REQUEST_TYPE_HEADER]: authRequestType, }); } catch (error: any) { console.log(error); diff --git a/server/readonly/readonly_service.ts b/server/readonly/readonly_service.ts index 337d04812..6e690b5f7 100644 --- a/server/readonly/readonly_service.ts +++ b/server/readonly/readonly_service.ts @@ -24,7 +24,6 @@ import { isPrivateTenant, LOGIN_PAGE_URI, CUSTOM_ERROR_PAGE_URI, - ANONYMOUS_AUTH_HEADER, } from '../../common'; import { SecurityClient } from '../backend/opensearch_security_client'; import { IAuthenticationType, OpenSearchAuthInfo } from '../auth/types/authentication_type'; diff --git a/test/jest_integration/basic_auth.test.ts b/test/jest_integration/basic_auth.test.ts index 00605adc9..f1f5ae16f 100644 --- a/test/jest_integration/basic_auth.test.ts +++ b/test/jest_integration/basic_auth.test.ts @@ -27,7 +27,6 @@ import { } from '../constant'; import { getAuthCookie, extractAuthCookie } from '../helper/cookie'; import wreck from '@hapi/wreck'; -import { ANONYMOUS_AUTH_HEADER } from '../../common'; describe('start OpenSearch Dashboards server', () => { let root: Root; @@ -141,8 +140,7 @@ describe('start OpenSearch Dashboards server', () => { it('can access home page as anonymous user', async () => { const response = await osdTestServer.request - .get(root, '/app/home#/') - .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + .get(root, '/app/home#/'); expect(response.status).toEqual(200); }); @@ -163,8 +161,7 @@ describe('start OpenSearch Dashboards server', () => { it('call authinfo API as anonymous user', async () => { const response = await osdTestServer.request - .get(root, '/api/v1/auth/authinfo') - .set(AUTHORIZATION_HEADER_NAME, ANONYMOUS_AUTH_HEADER); + .get(root, '/api/v1/auth/authinfo'); expect(response.status).toEqual(200); });