Skip to content

Commit 6d882c9

Browse files
Adds a session token to AWS Credentials (#6103)
* Adds session token for aws connection Signed-off-by: Bandini Bhopi <[email protected]> * Adds changelog Signed-off-by: Bandini Bhopi <[email protected]> --------- Signed-off-by: Bandini Bhopi <[email protected]>
1 parent ead2947 commit 6d882c9

8 files changed

+156
-37
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
2727
- [Multiple Datasource] Expose a few properties for customize the appearance of the data source selector component ([#6057](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6057))
2828
- [Multiple Datasource] Create data source menu component able to be mount to nav bar ([#6082](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6082))
2929
- [Multiple Datasource] Handle form values(request payload) if the selected type is available in the authentication registry ([#6049](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6049))
30+
- [Multiple Datasource] Adds a session token to AWS credentials ([#6103](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6103))
3031
- [Multiple Datasource] Add Vega support to MDS by specifying a data source name in the Vega spec ([#5975](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5975))
3132
- [Workspace] Consume workspace id in saved object client ([#6014](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6014))
3233
- [Multiple Datasource] Export DataSourcePluginRequestContext at top level for plugins to use ([#6108](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6108))

src/plugins/data_source/server/client/configure_client.test.mocks.ts

+9
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ export const authRegistryCredentialProviderMock = jest.fn();
2121
jest.doMock('../util/credential_provider', () => ({
2222
authRegistryCredentialProvider: authRegistryCredentialProviderMock,
2323
}));
24+
25+
export const CredentialsMock = jest.fn();
26+
jest.doMock('aws-sdk', () => {
27+
const actual = jest.requireActual('aws-sdk');
28+
return {
29+
...actual,
30+
Credentials: CredentialsMock,
31+
};
32+
});

src/plugins/data_source/server/client/configure_client.test.ts

+56-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ClientMock,
1818
parseClientOptionsMock,
1919
authRegistryCredentialProviderMock,
20+
CredentialsMock,
2021
} from './configure_client.test.mocks';
2122
import { OpenSearchClientPoolSetup } from './client_pool';
2223
import { configureClient } from './configure_client';
@@ -48,6 +49,17 @@ describe('configureClient', () => {
4849
let customApiSchemaRegistry: CustomApiSchemaRegistry;
4950
let authenticationMethodRegistery: jest.Mocked<IAuthenticationMethodRegistery>;
5051

52+
const customAuthContent = {
53+
region: 'us-east-1',
54+
roleARN: 'test-role',
55+
};
56+
57+
const authMethod: AuthenticationMethod = {
58+
name: 'typeA',
59+
authType: AuthType.SigV4,
60+
credentialProvider: jest.fn(),
61+
};
62+
5163
beforeEach(() => {
5264
dsClient = opensearchClientMock.createInternalClient();
5365
logger = loggingSystemMock.createLogger();
@@ -110,10 +122,12 @@ describe('configureClient', () => {
110122
};
111123

112124
ClientMock.mockImplementation(() => dsClient);
125+
authenticationMethodRegistery.getAuthenticationMethod.mockImplementation(() => authMethod);
113126
});
114127

115128
afterEach(() => {
116129
ClientMock.mockReset();
130+
CredentialsMock.mockReset();
117131
});
118132

119133
test('configure client with auth.type == no_auth, will call new Client() to create client', async () => {
@@ -251,12 +265,7 @@ describe('configureClient', () => {
251265
expect(decodeAndDecryptSpy).toHaveBeenCalledTimes(1);
252266
});
253267

254-
test('configureClient should retunrn client from authentication registery if method present in registry', async () => {
255-
const name = 'typeA';
256-
const customAuthContent = {
257-
region: 'us-east-1',
258-
roleARN: 'test-role',
259-
};
268+
test('configureClient should return client if authentication method from registry provides credentials', async () => {
260269
savedObjectsMock.get.mockReset().mockResolvedValueOnce({
261270
id: DATA_SOURCE_ID,
262271
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
@@ -269,12 +278,6 @@ describe('configureClient', () => {
269278
},
270279
references: [],
271280
});
272-
const authMethod: AuthenticationMethod = {
273-
name,
274-
authType: AuthType.SigV4,
275-
credentialProvider: jest.fn(),
276-
};
277-
authenticationMethodRegistery.getAuthenticationMethod.mockImplementation(() => authMethod);
278281

279282
authRegistryCredentialProviderMock.mockReturnValue({
280283
credential: sigV4AuthContent,
@@ -291,5 +294,46 @@ describe('configureClient', () => {
291294
expect(authenticationMethodRegistery.getAuthenticationMethod).toHaveBeenCalledTimes(1);
292295
expect(ClientMock).toHaveBeenCalledTimes(1);
293296
expect(savedObjectsMock.get).toHaveBeenCalledTimes(1);
297+
expect(CredentialsMock).toHaveBeenCalledTimes(1);
298+
expect(CredentialsMock).toBeCalledWith({
299+
accessKeyId: sigV4AuthContent.accessKey,
300+
secretAccessKey: sigV4AuthContent.secretKey,
301+
});
302+
});
303+
304+
test('When credential provider from auth registry returns session token, credentials should contains session token', async () => {
305+
const mockCredentials = { ...sigV4AuthContent, sessionToken: 'sessionToken' };
306+
savedObjectsMock.get.mockReset().mockResolvedValueOnce({
307+
id: DATA_SOURCE_ID,
308+
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
309+
attributes: {
310+
...dataSourceAttr,
311+
auth: {
312+
type: AuthType.SigV4,
313+
credentials: customAuthContent,
314+
},
315+
},
316+
references: [],
317+
});
318+
319+
authRegistryCredentialProviderMock.mockReturnValue({
320+
credential: mockCredentials,
321+
type: AuthType.SigV4,
322+
});
323+
324+
await configureClient(
325+
{ ...dataSourceClientParams, authRegistry: authenticationMethodRegistery },
326+
clientPoolSetup,
327+
config,
328+
logger
329+
);
330+
331+
expect(ClientMock).toHaveBeenCalledTimes(1);
332+
expect(CredentialsMock).toHaveBeenCalledTimes(1);
333+
expect(CredentialsMock).toBeCalledWith({
334+
accessKeyId: mockCredentials.accessKey,
335+
secretAccessKey: mockCredentials.secretKey,
336+
sessionToken: mockCredentials.sessionToken,
337+
});
294338
});
295339
});

src/plugins/data_source/server/client/configure_client.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
getCredential,
2727
getDataSource,
2828
generateCacheKey,
29+
getSigV4Credentials,
2930
} from './configure_client_utils';
3031
import { IAuthenticationMethodRegistery } from '../auth_registry';
3132
import { authRegistryCredentialProvider } from '../util/credential_provider';
@@ -199,11 +200,12 @@ const getBasicAuthClient = (
199200
};
200201

201202
const getAWSClient = (credential: SigV4Content, clientOptions: ClientOptions): Client => {
202-
const { accessKey, secretKey, region, service } = credential;
203+
const { accessKey, secretKey, region, service, sessionToken } = credential;
204+
const sigv4Credentials = getSigV4Credentials(accessKey, secretKey, sessionToken);
203205

204206
const credentialProvider = (): Promise<Credentials> => {
205207
return new Promise((resolve) => {
206-
resolve(new Credentials({ accessKeyId: accessKey, secretAccessKey: secretKey }));
208+
resolve(sigv4Credentials);
207209
});
208210
};
209211

src/plugins/data_source/server/client/configure_client_utils.ts

+19
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Client } from '@opensearch-project/opensearch';
77
import { Client as LegacyClient } from 'elasticsearch';
8+
import { Credentials } from 'aws-sdk';
89
import { SavedObjectsClientContract } from '../../../../../src/core/server';
910
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common';
1011
import {
@@ -145,3 +146,21 @@ export const generateCacheKey = (dataSourceAttr: DataSourceAttributes, dataSourc
145146

146147
return key;
147148
};
149+
150+
export const getSigV4Credentials = (
151+
accessKeyId: string,
152+
secretAccessKey: string,
153+
sessionToken?: string
154+
): Credentials => {
155+
let sigv4Credentials: Credentials;
156+
if (sessionToken) {
157+
sigv4Credentials = new Credentials({
158+
accessKeyId,
159+
secretAccessKey,
160+
sessionToken,
161+
});
162+
} else {
163+
sigv4Credentials = new Credentials({ accessKeyId, secretAccessKey });
164+
}
165+
return sigv4Credentials;
166+
};

src/plugins/data_source/server/legacy/configure_legacy_client.test.mocks.ts

-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,3 @@ export const parseClientOptionsMock = jest.fn();
1616
jest.doMock('./client_config', () => ({
1717
parseClientOptions: parseClientOptionsMock,
1818
}));
19-
20-
export const authRegistryCredentialProviderMock = jest.fn();
21-
jest.doMock('../util/credential_provider', () => ({
22-
authRegistryCredentialProvider: authRegistryCredentialProviderMock,
23-
}));

src/plugins/data_source/server/legacy/configure_legacy_client.test.ts

+62-15
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import { CryptographyServiceSetup } from '../cryptography_service';
1313
import { DataSourceClientParams, LegacyClientCallAPIParams, AuthenticationMethod } from '../types';
1414
import { OpenSearchClientPoolSetup } from '../client';
1515
import { ConfigOptions } from 'elasticsearch';
16+
import { ClientMock, parseClientOptionsMock } from './configure_legacy_client.test.mocks';
1617
import {
17-
ClientMock,
18-
parseClientOptionsMock,
1918
authRegistryCredentialProviderMock,
20-
} from './configure_legacy_client.test.mocks';
19+
CredentialsMock,
20+
} from '../client/./configure_client.test.mocks';
2121
import { configureLegacyClient } from './configure_legacy_client';
2222
import { CustomApiSchemaRegistry } from '../schema_registry';
2323
import { IAuthenticationMethodRegistery } from '../auth_registry';
@@ -47,6 +47,17 @@ describe('configureLegacyClient', () => {
4747

4848
const mockResponse = { data: 'ping' };
4949

50+
const customAuthContent = {
51+
region: 'us-east-1',
52+
roleARN: 'test-role',
53+
};
54+
55+
const authMethod: AuthenticationMethod = {
56+
name: 'typeA',
57+
authType: AuthType.SigV4,
58+
credentialProvider: jest.fn(),
59+
};
60+
5061
beforeEach(() => {
5162
mockOpenSearchClientInstance = {
5263
close: jest.fn(),
@@ -119,10 +130,13 @@ describe('configureLegacyClient', () => {
119130
response: mockResponse,
120131
});
121132
});
133+
134+
authenticationMethodRegistery.getAuthenticationMethod.mockImplementation(() => authMethod);
122135
});
123136

124137
afterEach(() => {
125138
ClientMock.mockReset();
139+
CredentialsMock.mockReset();
126140
jest.resetAllMocks();
127141
});
128142

@@ -263,12 +277,7 @@ describe('configureLegacyClient', () => {
263277
expect(mockOpenSearchClientInstance.ping).toHaveBeenLastCalledWith(mockParams);
264278
});
265279

266-
test('configureLegacyClient should retunrn client from authentication registery if method present in registry', async () => {
267-
const name = 'typeA';
268-
const customAuthContent = {
269-
region: 'us-east-1',
270-
roleARN: 'test-role',
271-
};
280+
test('configureLegacyClient should return client if authentication method from registry provides credentials', async () => {
272281
savedObjectsMock.get.mockReset().mockResolvedValueOnce({
273282
id: DATA_SOURCE_ID,
274283
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
@@ -281,12 +290,6 @@ describe('configureLegacyClient', () => {
281290
},
282291
references: [],
283292
});
284-
const authMethod: AuthenticationMethod = {
285-
name,
286-
authType: AuthType.SigV4,
287-
credentialProvider: jest.fn(),
288-
};
289-
authenticationMethodRegistery.getAuthenticationMethod.mockImplementation(() => authMethod);
290293

291294
authRegistryCredentialProviderMock.mockReturnValue({
292295
credential: sigV4AuthContent,
@@ -304,5 +307,49 @@ describe('configureLegacyClient', () => {
304307
expect(authenticationMethodRegistery.getAuthenticationMethod).toHaveBeenCalledTimes(1);
305308
expect(ClientMock).toHaveBeenCalledTimes(1);
306309
expect(savedObjectsMock.get).toHaveBeenCalledTimes(1);
310+
expect(CredentialsMock).toHaveBeenCalledTimes(1);
311+
expect(CredentialsMock).toBeCalledWith({
312+
accessKeyId: sigV4AuthContent.accessKey,
313+
secretAccessKey: sigV4AuthContent.secretKey,
314+
});
315+
});
316+
317+
test('When credential provider from auth registry returns session token, credentials should contains session token', async () => {
318+
const mockCredentials = { ...sigV4AuthContent, sessionToken: 'sessionToken' };
319+
savedObjectsMock.get.mockReset().mockResolvedValueOnce({
320+
id: DATA_SOURCE_ID,
321+
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
322+
attributes: {
323+
...dataSourceAttr,
324+
auth: {
325+
type: AuthType.SigV4,
326+
credentials: customAuthContent,
327+
},
328+
},
329+
references: [],
330+
});
331+
332+
authRegistryCredentialProviderMock.mockReturnValue({
333+
credential: mockCredentials,
334+
type: AuthType.SigV4,
335+
});
336+
337+
await configureLegacyClient(
338+
{ ...dataSourceClientParams, authRegistry: authenticationMethodRegistery },
339+
callApiParams,
340+
clientPoolSetup,
341+
config,
342+
logger
343+
);
344+
expect(authRegistryCredentialProviderMock).toHaveBeenCalled();
345+
expect(authenticationMethodRegistery.getAuthenticationMethod).toHaveBeenCalledTimes(1);
346+
expect(ClientMock).toHaveBeenCalledTimes(1);
347+
expect(savedObjectsMock.get).toHaveBeenCalledTimes(1);
348+
expect(CredentialsMock).toHaveBeenCalledTimes(1);
349+
expect(CredentialsMock).toBeCalledWith({
350+
accessKeyId: mockCredentials.accessKey,
351+
secretAccessKey: mockCredentials.secretKey,
352+
sessionToken: mockCredentials.sessionToken,
353+
});
307354
});
308355
});

src/plugins/data_source/server/legacy/configure_legacy_client.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Client } from '@opensearch-project/opensearch';
77
import { Client as LegacyClient, ConfigOptions } from 'elasticsearch';
8-
import { Credentials, Config } from 'aws-sdk';
8+
import { Config } from 'aws-sdk';
99
import { get } from 'lodash';
1010
import HttpAmazonESConnector from 'http-aws-es';
1111
import {
@@ -34,6 +34,7 @@ import {
3434
getCredential,
3535
getDataSource,
3636
generateCacheKey,
37+
getSigV4Credentials,
3738
} from '../client/configure_client_utils';
3839
import { IAuthenticationMethodRegistery } from '../auth_registry';
3940
import { authRegistryCredentialProvider } from '../util/credential_provider';
@@ -230,12 +231,13 @@ const getBasicAuthClient = async (
230231
};
231232

232233
const getAWSClient = (credential: SigV4Content, clientOptions: ConfigOptions): LegacyClient => {
233-
const { accessKey, secretKey, region, service } = credential;
234+
const { accessKey, secretKey, region, service, sessionToken } = credential;
235+
const credentials = getSigV4Credentials(accessKey, secretKey, sessionToken);
234236
const client = new LegacyClient({
235237
connectionClass: HttpAmazonESConnector,
236238
awsConfig: new Config({
237239
region,
238-
credentials: new Credentials({ accessKeyId: accessKey, secretAccessKey: secretKey }),
240+
credentials,
239241
}),
240242
service,
241243
...clientOptions,

0 commit comments

Comments
 (0)