Skip to content

Commit

Permalink
Multiauth proxy support
Browse files Browse the repository at this point in the history
Signed-off-by: Stephen Crawford <[email protected]>
  • Loading branch information
stephen-crawford committed Jul 29, 2024
1 parent 2af71cf commit 3be0167
Show file tree
Hide file tree
Showing 6 changed files with 494 additions and 6 deletions.
15 changes: 11 additions & 4 deletions public/apps/login/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,11 @@ export function LoginPage(props: LoginPageDeps) {
renderLoginButton(AuthType.ANONYMOUS, ANONYMOUS_AUTH_LOGIN, anonymousConfig)
);
}

formBody.push(<EuiSpacer size="xs" />);
formBody.push(<EuiHorizontalRule size="full" margin="xl" />);
formBody.push(<EuiSpacer size="xs" />);
if (!authOpts.includes(AuthType.PROXY) || authOpts.length !== 2) {
formBody.push(<EuiSpacer size="xs" />);
formBody.push(<EuiHorizontalRule size="full" margin="xl" />);
formBody.push(<EuiSpacer size="xs" />);
}
}
break;
}
Expand All @@ -238,6 +239,12 @@ export function LoginPage(props: LoginPageDeps) {
formBodyOp.push(renderLoginButton(AuthType.SAML, samlAuthLoginUrl, samlConfig));
break;
}
case AuthType.PROXY: {
// formBody.pop();
// formBody.pop();
// formBody.pop();
break;
}
default: {
setloginFailed(true);
setloginError(
Expand Down
17 changes: 15 additions & 2 deletions server/auth/types/multiple/multi_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ANONYMOUS_AUTH_LOGIN, AuthType, LOGIN_PAGE_URI } from '../../../../comm
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';

Check failure on line 32 in server/auth/types/multiple/multi_auth.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

Replace `·BasicAuthentication,·OpenIdAuthentication,·ProxyAuthentication,·SamlAuthentication·` with `⏎··BasicAuthentication,⏎··OpenIdAuthentication,⏎··ProxyAuthentication,⏎··SamlAuthentication,⏎`

Check failure on line 32 in server/auth/types/multiple/multi_auth.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (windows-latest)

Replace `·BasicAuthentication,·OpenIdAuthentication,·ProxyAuthentication,·SamlAuthentication·` with `␍⏎··BasicAuthentication,␍⏎··OpenIdAuthentication,␍⏎··ProxyAuthentication,␍⏎··SamlAuthentication,␍⏎`

export class MultipleAuthentication extends AuthenticationType {
private authTypes: string | string[];
Expand Down Expand Up @@ -93,6 +93,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]}`);
}
Expand All @@ -115,7 +128,7 @@ export class MultipleAuthentication extends AuthenticationType {
async getAdditionalAuthHeader(
request: OpenSearchDashboardsRequest<unknown, unknown, unknown, any>
): Promise<any> {
// 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();

Expand Down
93 changes: 93 additions & 0 deletions server/auth/types/proxy/proxy_auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { httpServerMock } from '../../../../../../src/core/server/http/http_server.mocks';

Check failure on line 1 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

File must start with a license header

Check failure on line 1 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (windows-latest)

File must start with a license header
import { IRouter, OpenSearchDashboardsRequest } from '../../../../../../src/core/server/http/router';

Check failure on line 2 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

Replace `·IRouter,·OpenSearchDashboardsRequest·` with `⏎··IRouter,⏎··OpenSearchDashboardsRequest,⏎`

Check failure on line 2 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (windows-latest)

Replace `·IRouter,·OpenSearchDashboardsRequest·` with `␍⏎··IRouter,␍⏎··OpenSearchDashboardsRequest,␍⏎`
import { SecurityPluginConfigType } from '../../../index';
import { SecuritySessionCookie } from '../../../session/security_cookie';
import { deflateValue } from '../../../utils/compression';
import {
CoreSetup,
ILegacyClusterClient,
Logger,
SessionStorageFactory,
} from '../../../../../../src/core/server';
import { ProxyAuthentication } from './proxy_auth';

describe('ProxyAuthentication', () => {
let proxyAuthentication: ProxyAuthentication;
let router: IRouter;
let core: CoreSetup;
let esClient: ILegacyClusterClient;
let sessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>;
let logger: Logger;

beforeEach(() => {
// Set up mock dependencies
router = {} as IRouter;
core = {} as CoreSetup;
esClient = {} as ILegacyClusterClient;
sessionStorageFactory = {} as SessionStorageFactory<SecuritySessionCookie>;
logger = {} as Logger;

const config = ({
proxy: {
extra_storage: {
cookie_prefix: 'testcookie',
additional_cookies: 5,
},
},
} as unknown) as SecurityPluginConfigType;

proxyAuthentication = new ProxyAuthentication(
config,
sessionStorageFactory,
router,
esClient,
core,
logger
);
});

it('should build auth header from cookie with authHeaderValue', () => {

Check failure on line 49 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (ubuntu-latest)

Delete `⏎`

Check failure on line 49 in server/auth/types/proxy/proxy_auth.test.ts

View workflow job for this annotation

GitHub Actions / Run unit tests (windows-latest)

Delete `␍⏎`

const cookie: SecuritySessionCookie = {
credentials: {
authHeaderValue: 'Bearer eyToken',
},
};

const expectedHeaders = {
authorization: 'Bearer eyToken',
};

const headers = proxyAuthentication.buildAuthHeaderFromCookie(cookie);

expect(headers).toEqual(expectedHeaders);
});

it('should get authHeaderValue from split cookies', () => {
const testString = 'Bearer eyCombinedToken';
const testStringBuffer: Buffer = deflateValue(testString);
const cookieValue = testStringBuffer.toString('base64');
const cookiePrefix = 'testcookie';
const splitValueAt = Math.ceil(cookieValue.length / 5);
const mockRequest = httpServerMock.createRawRequest({
state: {
[cookiePrefix + '1']: cookieValue.substring(0, splitValueAt),
[cookiePrefix + '2']: cookieValue.substring(splitValueAt),
},
});
OpenSearchDashboardsRequest.from(mockRequest);
const cookie: SecuritySessionCookie = {
credentials: {
authHeaderValueExtra: true,
},
};

const expectedHeaders = {
authorization: testString,
};

const headers = proxyAuthentication.buildAuthHeaderFromCookie(cookie);

expect(headers).toEqual(expectedHeaders);
});
});
3 changes: 3 additions & 0 deletions test/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ export const ADMIN_PASSWORD: string = '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';
101 changes: 101 additions & 0 deletions test/jest_integration/proxy_auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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, expect, it, beforeAll, afterAll } from '@jest/globals';
import {
OPENSEARCH_DASHBOARDS_SERVER_USER,
OPENSEARCH_DASHBOARDS_SERVER_PASSWORD,
ADMIN_USER,
PROXY_USER,
PROXY_ROLE,
PROXY_ADMIN_ROLE,
} from '../constant';

describe('start OpenSearch Dashboards server', () => {
let root: Root;

beforeAll(async () => {
root = osdTestServer.createRootWithSettings(
{
plugins: {
scanDirs: [resolve(__dirname, '../..')],
},
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: {
type: 'proxy',
},
proxycache: {
user_header: 'x-proxy-user',
roles_header: 'x-proxy-roles',
},
},
},
{
// to make ignoreVersionMismatch setting work
// can be removed when we have corresponding ES version
dev: true,
}
);
});

afterAll(async () => {
// shutdown OpenSearchDashboards server
await root.shutdown();
});

it('can access home page with proxy header', async () => {
const response = await osdTestServer.request
.get(root, 'app/home#/')
.set(PROXY_USER, ADMIN_USER)
.set(PROXY_ROLE, PROXY_ADMIN_ROLE);
expect(response.status).toEqual(200);
});

it('cannot access home page without proxy header', async () => {
const response = await osdTestServer.request.get(root, 'app/home#/');
expect(response.status).toEqual(401);
});

it('cannot access home page with partial proxy header', async () => {
const response = await osdTestServer.request
.get(root, 'app/home#/')
.set(PROXY_USER, ADMIN_USER);
expect(response.status).toEqual(401);
});

it('cannot access home page with partial proxy header2', async () => {
const response = await osdTestServer.request
.get(root, 'app/home#/')
.set(PROXY_ROLE, PROXY_ADMIN_ROLE);
expect(response.status).toEqual(401);
});
});
Loading

0 comments on commit 3be0167

Please sign in to comment.